Rubyでハローワールドする30の方法を全解説する
前説
この記事はRuby30イベントで私が発表した「Rubyでハローワールドする30の方法」の解説記事です。LTは5分しかなかったので説明がほとんどできなかったため、その補足として書かれました。お楽しみください。
蛇足
この記事はスクリプトによって生成されました。全貌はレポジトリからご覧になれます。
本編
スライドのURLは以下。
RubyでHello worldする方法その1
# 一番単純
puts "Hello, world!"
解説不要。
RubyでHello worldする方法その2
# 文字列埋め込み
hello = "Hello"
world = 'world!'
puts "#{hello}, #{world}"
文字列の埋め込みは頻出。
RubyでHello worldする方法その3
# フォーマット文字列、`ord`で得られた数字を使う
printf "%s, %c%c%c%c%c%c%c", "Hello", 119, 111, 114, 108, 100, 33, 10
フォーマット文字列はあまり使ったことがない。%c
の裏で動いているのはchr
メソッドっぽい。10
は改行コード、見た目からは全くわからない。
RubyでHello worldする方法その4
# 標準出力($>)に文字列を追加する
$><<"Hello, world!\n"
標準出力の$>
表記はつい最近知った。それに<<
で文字列を追加するとputs
のようになる。ゴルファー御用達。
RubyでHello worldする方法その5
# 正規表現でキャプチャして連結
str = "Hello, ruby30 world!"
md = str.match /^(\w+).+(\w{5,}!)/
puts md.captures.join(', ')
正規表現の右側のキャプチャ部分(world部分)に注目すると、ここで文字数を明示的に5文字以上にしないとうまくいかなかった。
RubyでHello worldする方法その6
# シンボルのリテラルにもいくつか書き方がある
print %s|Hello, world|
puts :!
シンボルリテラルをこんな書き方したことない。:!
が使えるのも知らなかった。
RubyでHello worldする方法その7
# Rubyには「文字」リテラルがあるのでそれらを結合する
puts ?H << ?e << ?l << ?l << ?o << ?, << ?\u0020 << ?w << ?o << ?r << ?l << ?d << ?!
パーセントリテラルで空白文字を表現するのは不可能だと思い込んでいたけど、ユニコード文字列っぽいやつが使えた、便利。
RubyでHello worldする方法その8
# ヒアドキュメントで縦書きを実現
puts <<"HELLOWORLD".chomp.gsub(/([a-zA-Z, ])?\n/) {|match| match[0]}
H
e
l
l
o
,
\x20
w
o
r
l
d
!
HELLOWORLD
日本人だから縦書きをしたい。ヒアドキュメントの文字列にメソッドを呼ぶ場合は最初の行に書く。gsub
にブロックを渡すのは普通に便利。
RubyでHello worldする方法その9
# 文字列の生成方法は色々ある
puts %q(Hello) + String.new(', ') + String(:world!)
文字列リテラルの書き方はたくさんある。String.new
はここで人生初の利用。String
メソッドもめったに使わない。
RubyでHello worldする方法その10
# "Hello, world!".each_codepoint.map{|n| n.to_s(16) }
# で得られたコードポイントの配列を文字列に戻す
["48", "65", "6c", "6c", "6f", "2c", "20", "77", "6f", "72", "6c", "64", "21", "a"].each do |codepoint|
print codepoint.to_i(16).chr
end
また別の文字の表現方法。最後の"a"は改行なのでprint
で良い。
RubyでHello worldする方法その11
# evalするだけだが、よく見るとネストしている
eval("eval %q(puts 'Hello, world!')")
evalだけだとありがちなのでネストさせてみた。
RubyでHello worldする方法その12
# evalの仲間のinstance_evalを使う、selfはHello, world!
"Hello, world!".instance_eval do
puts self
end
instance_eval
を使うことでputs self
と書けるのはかっこいい。
RubyでHello worldする方法その13
# thenとmethodメソッドのProc化を組み合わせる
"Hello, world!".then(&method(:puts))
method(:puts)
でputs
メソッドをオブジェクトとして取得し、それを&
でProc
に変換してthen
に渡す。tap
でも可。
RubyでHello worldする方法その14
# pはinspectを内部で呼ぶので、inspectが文字列を返せばよい
str = ""
def str.inspect
"Hello, world!"
end
p str
空文字列のオブジェクトに特異メソッドとしてinspect
を定義し、p
が内部でそれを呼ぶ。
RubyでHello worldする方法その15
# putsは内部でto_sを呼ぶので、to_sがHello, world!を返せばよい
class Okura
def name = "OKURA Masafumi"
def work_as = "Freelancer"
def available_for_hiring? = true
def organizer_of = "Kaigi on Rails"
def to_s
"Hello, world!"
end
end
puts Okura.new
自己紹介のためのクラスだが、to_s
をオーバーライドすることでputs
の引数になったときの挙動を変更している。
RubyでHello worldする方法その16
# 全てのputsがHello, world!になる
module HelloWorld
def puts(*args)
super("Hello, world!")
end
end
Kernel.prepend(HelloWorld)
puts
Kernel.prepend
するとグローバルな関数の挙動を変更できてしまう。puts
の内部でsuper
を呼ぶことで本物のputs
を偽物から使うことができる。
RubyでHello worldする方法その17
# 素直に新しいメソッドを定義する
module HelloWorld
def put_hello_world
puts("Hello, world!")
end
end
Kernel.include(HelloWorld)
put_hello_world
Kernel.include
でグローバルな関数を定義できる。素直。
RubyでHello worldする方法その18
# Stringをオープンクラスする
class String
def print_self
puts self
end
end
"Hello, world!".print_self
Rubyのクラスは再オープン可能なので、どんなクラスにもメソッドを追加できる。
RubyでHello worldする方法その19
# オープンクラスはお行儀が悪いのでrefinementsを使う
using Module.new {
refine String do
def print_self
puts self
end
end
}
"Hello, world!".print_self
組み込みクラスの再オープンはあまり良くないのでRefinementsを使うほうが良い…か?
RubyでHello worldする方法その20
# メソッドチェーンは便利
class String
("a".."z").each do |char|
define_method(char) { self + char }
end
def comma = self + ?,
def space = self + ' '
def ! = self + ?!
def puts = Kernel.puts(self)
end
"H".e.l.l.o.comma.space.w.o.r.l.d.!.puts
まずは"Hello, world!"の各文字に対応するメソッドをStringクラスに定義し、メソッド名を自身に連結する。カンマとスペースはそのままでは動かないので別途メソッドを定義する。あとは文字ごとのメソッドをチェインするだけ。
RubyでHello worldする方法その21
# 文字列は隣接させると連結される
# %リテラルの区切り文字には空白が使える
# Thanks @tompng
puts(% Hello, ' world'"!")
文字列が隣接していると合体することを利用し、空白で囲まれた%リテラルにシングルクオートとダブルクオートの文字を連結している。
RubyでHello worldする方法その22
# 無意味なオブジェクト指向
class Printer
def initialize(object)
@object = object
end
def print
Kernel.print @object
puts
end
end
Printer.new("Hello, world!").print
オブジェクト指向設計。puts
を空打ちして改行を補っている。print
内で素朴にprint
を呼ぶと無限ループするので、Kernel.print
を呼んでいる。
RubyでHello worldする方法その23
# putsはKernelのインスタンスメソッドなので、適当なオブジェクトにbindできる
Kernel.instance_method(:puts).bind(Object.new).call("Hello, world!")
instance_method
メソッドでputs
メソッドをオブジェクトとして取得する。そのままではcall
できないので適当なオブジェクトにbind
してからcall
する。main
の文脈でinstance_method
を呼ぶのにKernel
が必要。
RubyでHello worldする方法その24
# どうしてこうなるのかわからない
# Dummyクラスは警告抑制に必要、sayメソッドはないとSyntaxError
class Dummy
def method_missing(meth, *args, &blk)
puts meth
end
def self.const_missing(name)
print name.to_s + ', '
end
def say(*)
end
end
Dummy.new.instance_eval("say Hello, world!")
main
のmethod_missing
をオーバーライドすると警告がでるのでDummy
クラスを使う。say
の後ろに"Hello, world!"を配置すると引数として解釈され、先に"Hello"のconst_missing
が呼ばれる。次に"world!"のmethod_missing
が呼ばれる。これをDummy
内で行うためにinstance_eval
を使っている。
RubyでHello worldする方法その25
# クラス名は文字列の代わりになる
class Hello
end
class World
def self.to_s = 'world!'
end
print "#{Hello}, #{World}\n"
文字列の埋め込みはなんでも使える。具体的にはto_s
が呼ばれるだけなので、クラス名が"Hello"ならそのまま埋め込める。"!"は定数名には使えないので、to_s
をオーバーライドしている。
RubyでHello worldする方法その26
# world!の返り値を、world!の中で定義したhelloメソッドで利用する
def world!
def hello(str)
puts "#{__method__.to_s.capitalize}, #{str}"
end
__method__.to_s
end
hello world!
hello world!
は右から評価され、world!
メソッドが実行されると、その中でhello
メソッドが定義される。hello
メソッドは引数を受け取るがその引数は定義の真下にあるworld!
メソッドの返り値がそのまま来る。__method__
はそのメソッドの名前のシンボルを返すので、to_s
して結合する。
RubyでHello worldする方法その27
# exitしてもハローワールドはできる
at_exit { puts "world!" }
print "Hello, "
exit
途中まで出力してexit
で終了するが、at_exit
のブロックが実行されて残りの文字列が出力される。
RubyでHello worldする方法その28
# 文字列をバラしてから復元する
ORDER = {
"H": [1],
"e": [2],
"l": [3, 4, 11],
"o": [5, 9],
",": [6],
" ": [7],
"w": [8],
"r": [10],
"d": [12],
"!": [13]
}
puts "Hello, world!".chars.shuffle.each_with_object("a"*13) { |char, result|
index = ORDER[char.to_sym]
result[index.shift - 1] = char
}
文字列の位置をハッシュで管理する。"Hello, world!
"の文字列をshuffle
しても復元できる。復元用の文字列として"a"だけの文字列を用意し、インデックスを指定して文字を置き換えている。
RubyでHello worldする方法その29
# 変数名を使う、大文字や空白、!は使えないので工夫が必要
hello = nil
world = nil
binding.local_variables.each_with_index do |var, i|
i.zero? ? (print var.capitalize) : (puts ", #{var}!")
end
ローカル変数を定義し、local_variables
を使うことで変数名を値として使う。"Hello"の大文字部分などはcapitalize
を使ってなんとかしている。
RubyでHello worldする方法その30
# Fiberで記号に関して処理を中断して、記号を入れたら再開する
f = Fiber.new do
"Helloworld".each_char.with_index do |c, idx|
print c
Fiber.yield if idx == 4 || idx == 10
end
end
f.resume
print ", "
f.resume
puts "!"
"Helloworld"という記号なし文字列を順番に表示するが、記号が入るべき位置でFiber.yield
を呼んで元のFiberに復帰する。記号を出力するとFiber#resume
でまた文字列の出力を再開する。
Discussion