Rubyの[1, 2, 3].map(&:to_s)的なイディオムとSymbol#to_procについて
タグ: ruby / 初版公開: 2014-02-09

ちょっと凝ったRubyのソースコードで良く目にする[1, 2, 3].map(&:to_s)のようなイディオムについて書いておきたい。まず以下はすべて同じ結果となる。

[1, 2, 3].map(&:to_s) #=> ["1", "2", "3"]
[1, 2, 3].map(&:to_s.to_proc) #=> ["1", "2", "3"]
[1, 2, 3].map{|n| n.to_s} #=> ["1", "2", "3"]

このイディオムはSymbolto_procメソッドを持っているから実行可能になっている。(※標準で使えるのはRuby 1.8.7以降、それ以前はActiveSupportで定義されていた) というのもブロック付きメソッドを呼び出す際にto_procメソッドを持つオブジェクトは&修飾して引数に渡せる決まりだからである。このことはリファレンスに書かれている。

to_proc メソッドを持つオブジェクトならば、’&’ 修飾した引数として渡すことができます。デフォルトで Proc、Method オブジェ クトは共に to_proc メソッドを持ちます。to_proc はメソッド呼び出し時に実行され、Proc オブジェクトを返すことが期待されます。

そしてSymbol#to_procの実装は以下のようになっているらしい。(※実際はC言語による実装)これを見ると冒頭のイディオムも理解しやすいのではないだろうか。読み解くとSymbol#to_procは何かのオブジェクトを引数にとって、何かのオブジェクトに対してシンボルと同名のメソッドを呼び出すProcオブジェクトを返すのだ。

class Symbol
  def to_proc
    Proc.new do |obj, *args|
      obj.send self, *args
    end
  end
end

つまりブロック付きメソッド呼び出しの()内の&:to_s&:to_s.to_procと同じであり、このProcオブジェクトの処理内容は{|obj, &args| obj.send :to_s, *args}であるかのように振る舞うのである。以上のことから、このイディオムが[1, 2, 3].map{|n| n.to_s}と同じ結果になることが理解できる。