先日、こんな感じの処理に遭遇しました。はて、、何じゃこりゃ・・(´・ω・`) ポカーン

(1..5).each(&method(:puts))

と思って調べたのでまとめておきます。この処理が理解できれば一人前です。きっと。

ブロックはオブジェクトではない

Ruby では大抵のものがオブジェクトですが、オブジェクトではないものも一部あります。ブロックがその一つです。

例えばこんな風にブロックを定義することは出来ません。

block = { "test" } # in:1: odd number list for Hash

そもそも Ruby の構文ではハッシュとして扱われるためブロックを直接生成することは出来ません。Block.new みたいな構文も無いので、ブロックは times とか each とか map のようなメソッド呼び出しのときにしか利用することが出来ないんです。

ブロックをオブジェクトとして扱いたい

でも生成したりとか変数に入れたりとか出来ないと色々と不便です。そういう理由でブロックとよく似た Proc という概念があります。ブロックをオブジェクト化したものが Proc です。

ブロックを Proc オブジェクトにするのは実は簡単で、メソッド定義の仮引数の最後に & 付きパラメーターを追加することで、Ruby がこのパラメーター (&hoge) をブロックとして扱い、hoge は Proc オブジェクトへの参照を持ちます。この辺は Ruby の黒魔術?ですね。

def sample_method(&hoge)
  p hoge      # hoge → Procオブジェクト, &hoge → ブロック ・・・★
  p hoge.call # "test"
end

sample_method { "test" } # ブロックを引数に

Symbol#to_proc

ちょっと話が変わりますが、今回の話を理解するためにはこれがどんな風に処理されているのかを理解している必要があります。わかるでしょうか?

%w(1 2 3 4 5).map(&:to_i)

通常、map メソッドはブロックを引数に取ります。そのため、mapの引数である &:to_i はブロックじゃないといけない わけです。

さらに、&:to_i がブロックということは、:to_i は Proc オブジェクトであることが期待されるわけです(↑の★のとこ参照)。そのため、:to_i に対して暗黙的に to_proc メソッド (Symbol#to_proc) が呼ばれます。

Symbol#to_proc の処理など、詳しくはこちらの記事をご覧下さい。
Rubyで "&" を使うと幸せになれるらしいよ (*´Д`)ノ - (゚∀゚)o彡 sasata299's blog

(1..5).each(&method(:puts)) について

やっと本題です。

同じように each メソッドはブロックを引数に取ります。そのため &method(:puts) はブロックであること、つまり method(:puts) が Proc オブジェクトであることが期待されます。

では、method(:puts) とは一体何なのでしょうか。実はメソッドはブロックと同じでオブジェクトではありませんが、method メソッド (Object#method) を使うことで Method オブジェクトへ変換されます。

class Foo
  def hello
    puts "Hello, world"
  end
end

f = Foo.new
m = f.method(:hello) # Methodオブジェクト
m.call               # "Hello, world"

method(:puts) に対して to_proc メソッド (Method#to_proc) が暗黙的に呼ばれます。Method#to_proc は self を call する Proc オブジェクトを生成して返します。

結局、最終的にはこういう処理が行われるわけですな。

(1..5).each { |num| puts num }

to_procメソッドを用意すれば・・

今回は Symbol オブジェクトと Method オブジェクトの例を紹介しましたが、他のクラスでも to_proc メソッドさえ用意してあげれば同様の処理が出来ます。例えば String クラスを拡張してみましょう。

class String
  def to_proc
    o,n = split(//)
    Proc.new { |num| num.__send__(o, n.to_i) }
  end
end

p (1..5).map(&'*2') # [2, 4, 6, 8, 10]

(ものすごく適当な実装ですが)とても簡単ですね 〜 (●´ω`●)
このエントリーをはてなブックマークに追加