BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ ニュース Ruby 1.9 のSymbol#to_procと(入ったばかりの)Procのカリー化

Ruby 1.9 のSymbol#to_procと(入ったばかりの)Procのカリー化

Ruby 1.9での目玉機能といえばスレッド機構の変更やFiber(source)だが、これ以外にも標準ライブラリに加えられた便利な機能がある。Object#tap(source) については既に紹介した通りだ。tapメソッドはメソッドコールのチェインの途中に処理を割り込ませられる便利な機能だ。

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_proc
puts plus.call(1,2) # '3'が表示される

Symbolクラスにto_procメソッドがあるということは、このやり方があらゆるシンボルに通用するということだ。
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)とは次のようなものだ。
全然難しいことはなくて、
 proc {|x, y, z| x + y + z }.curry
 は、次のようなprocを返すのと同じです
 proc {|x| proc {|y| proc {|z| x + y + z } } }
メソッド名の由来は「カリー化(Currying)(source)」だ。カリー化とは、

カリー化(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

この記事に星をつける

おすすめ度
スタイル

BT