tapメソッドがそうであったように、to_procメソッドのアイデアも、少し前からRubyコミュニティでは知られていた。to_proc相当の機能は、Ruby 1.9より前のバージョンでも利用できたのだ。Ruby 1.9 はこの機能をそのままSymbolクラスへと統合した。このおかげで、Symbol#to_procを使うのにライブラリは必要なくなった。
Reg Braithwaite による手短なSymbol#to_procの紹介記事では (1..100).inject(&:+) のようなコードが動くようになったと説明(source)されている。
&演算子はProcオブジェクトをブロックへ変換し、ブロックをProcオブジェクトへ変換します。この場合、&はシンボル :+ をブロックへ変換しようとします。ブロックへの変換にはRuby組み込みの型変換が利用されます。ブロックへの型変換ではまず、Procオブジェクトが与えられているかをチェックします。ブロックが与えられていなければ、引数として与えられたオブジェクトをProcへと変換すべく to_procメソッドが呼び出されます。このとき、シンボル :+ に to_proc メソッドが定義されていれば、それが呼び出されます。Ruby 1.9では、Symbol#to_roc が定義されています。このto_procメソッドは、Procオブジェクトを返します。
つまり、&:+
は{ |x, y| x + y }
になるということです。
この振る舞いのおかげで次のようなコードを書けるようになる。
plus = :+.to_procSymbolクラスにto_procメソッドがあるということは、このやり方があらゆるシンボルに通用するということだ。
puts plus.call(1,2) # '3'が表示される
to_s = :to_s.to_proc
to_s.call(42) # 文字列"42"が返ってくる
to_procの実装はシンプルだ。Dave Thomas(PragDave)はその動作(source)をこう説明している。
def to_proc
proc { |obj, *args| obj.send(self, *args) }
end
to_procメソッドはProcを生成します。生成されたProcは、オブジェクトを引数にして呼び出されると、渡されたオブジェクトに対してsendメソッドを呼び出します。sendメソッドの第1引数のシンボルは、to_procのレシーバの自分自身です。ですから、たとえば names.map(&:upcase)の場合、namesに格納されている字列をイテレートするわけですが、その際にはnamesの各要素に対してupcaseメソッドを呼び出すブロックがコールバックされます。
この機能のおかげでコードがとても簡潔になるのだが、ひとつ疑問が残る。つまり、これでコードは読みやすくなるのだろうか?これに対する答えはたぶん to_proc がRuby 1.9の標準となったことで変わっていくだろう。なぜなら、いまやSymbolにはかならずto_procメソッドがあるからだ。以前であれば、Symbol#to_procを実現しようとすれば、自分でコードを書くか、Symbolを再オープンしてto_procメソッドを追加するライブラリを使うしかなった。さらに、こうしたコードを理解するにはSymbol#to_procというイディオムを知っていなければならなかった。これについてはRuby 1.9で公式にサポートされたことで、今後はドキュメントでも言及されていくようになるだろう。
それでもやはり疑問は残る。確かに、
(1..100).map(&:to_s)
は
(1..100).map{|x| x.to_s }よりも短く書ける(この例では5文字短かい)。しかし、これは複雑さを増すに見合ったものだろうか? それともやりすぎだろうか?
Ruby 1.9のProcには他にも追加された機能がある。Porc#curry がそうだ(といってもsvnリポジトリのごく最近のリビジョン(r15459以降)でなければ使えないが)。ruby-core メーリングリストの議論によれば、Proc#curry の機能(source)とは次のようなものだ。
全然難しいことはなくて、メソッド名の由来は「カリー化(Currying)(source)」だ。カリー化とは、
proc {|x, y, z| x + y + z }.curry
は、次のようなprocを返すのと同じです
proc {|x| proc {|y| proc {|z| x + y + z } } }
カリー化(currying)とは、計算機科学分野の技法の一つ。複数の引数をとる関数を、引数が「もとの関数の最初の引数」で戻り値が「もとの関数の残りの引数を取り結果を返す関数」であるような関数にすること。
つまりカリー化とは、n個の引数を取るProcを1つの引数だけで呼ぶことができるということだ。この場合は(当たり前だが)処理の結果を返すことはできない――処理を実行するために必要な引数が与えられていないからだ。よって、処理結果の代わりに、(n -1)個の引数をとる新しいProcが返される。これが繰り返されて、Procのコードを処理するのに必要なだけの引数が揃ったら、Procは処理を実行してその結果を返す。
ruby-core メーリングリストの議論スレッドから利用例を紹介しよう。
plus_five = proc { |x,y,z| x + y + z }.curry.call(2).call(3)
plus_five[10] #=> 15
補足: plus_five は Procであり、[]演算子はProcではProcを実行するようにオーバーライドされている。
注意: Proc#curry が Ruby 1.9 に追加されたのはつい最近である。試してみるには、最新のsvnリビジョン(r15459以降)を利用しなければならない。
原文はこちらです:http://www.infoq.com/news/2008/02/to_proc-currying-ruby19