Method#to_proc の動作(Ruby)
前提
ruby-2.0.0-p353
Method#to_proc の動作に納得したい
(1..5).each do |i| puts i end
(1..5).each(&method(:puts))
上記2つのコードは同じ動作をする。パッと見で納得できなかったので動作を1つずつ追ってみる。
まず method(:puts)
。 Object#method
を使って Method オブジェクトを作っているわけだけど、Method オブジェクトはレシーバとメソッドの組み合わせで保存される。この場合ではレシーバが「Ruby のトップレベルの Object オブジェクト」、メソッドが「puts メソッド」の組み合わせで Method オブジェクト化している。
この Method オブジェクトを &
記法を使って、each メソッドにブロックとして渡している。Method オブジェクトのままブロックとして渡せないので、ここで暗黙的に Method#to_proc
が呼ばれ、Proc オブジェクトへ変換される。
では、どういう Proc オブジェクトになるのか。
Method#to_proc
はるりまを見ると「self を call する Proc オブジェクトを生成して返します。」と説明されている。ここでの self
とは to_proc
を呼ばれる Method オブジェクト自身のことだ。
ということは単純に考えると以下の a_proc の様になる。
m = method(:puts) a_proc = Proc.new { m.call }
しかしこれは正解ではない。引数が考慮されていないから、この a_proc を each メソッドに &
記法で渡すと、何も引数を受け取らずにただ改行だけ出力するブロックが繰り返し実行されてしまう。
puts
は必須引数がなく、可変長引数を取る。Method オブジェクト化した puts
に Method#arity
を使うと -1 が返ってくる。それは Proc オブジェクト化した後も変わらない。
よって、きちんと動かすにはこういう Proc オブジェクトになっているはずだ。
m = method(:puts) a_proc = Proc.new {|*i| m.call(*i) }
実際に Method#to_proc
で変換された Proc オブジェクトがこういう形をしているかどうかは分からないけど、多分間違っていないはず。
試してみるとちゃんと動きました。
m = method(:puts) a_proc = Proc.new {|*i| m.call(*i) } (1..5).each(&a_proc) 1 2 3 4 5 #=> (1..5)
(この記事は最初、変数 a_proc を proc にしてたんだけど、Kernel.procがあるからバッティングしていた…。)