Melborne さんの華麗な技巧を鑑賞する(Ruby)
落ちていくRubyistのためのMethopオブジェクト
すごく技巧的なので驚きました。何だかよくわからなかったので、調べてみました。
まずコピペです。
class Methop def self.[](method) new(method).build end def initialize(method) @method = method end def build ->arg, obj{ obj.send(@method, arg) }.curry end end Plus = Methop[:+] %w(ruby violin novel).map(&Plus['ist']) #=>["rubyist", "violinist", "novelist"]
補足などして、多少書き直してみます。
class Methop def self.[](method) a = Methop.new(method) a.build end def initialize(method) @method = method end def build lambda {|arg, obj| obj.send(@method, arg)}.curry end end plus = Methop[:+] p %w(ruby violin novel).map &plus.call('ist') #=>["rubyist", "violinist", "novelist"]
まずクラス・メソッド self.[](method)
ですが、角括弧演算子の再定義はこのようにします(参照)。クラス・メソッドなので、その下の new
は前にクラス Methop が省略されています。
インスタンス・メソッド build
は、カリー化された Proc オブジェクトを返します。カリー化しているのは、引数 obj
が後で map によって与えられるからで、これがないとこの場で引数の数が合わないというエラーになります。Object#send メソッドは、インスタンス変数 @method
によって与えられるメソッドを(引数 arg
を付けて)実行します。
変数 plus
にはインスタンス・メソッド build
によって与えられる Proc オブジェクトが入ります。インスタンス変数 @method
には、演算子 + が(ここでは Symbol として)入ります。
map 実行時に、レシーバーである Array の要素が次々に Proc オブジェクトの引数 obj
に入ります。plus.call('ist')
で引数 arg
に "ist" が入り、Proc オブジェクトが実行されます。前に付いている &
は、その Proc オブジェクトをブロックに直し、map に与えています(参照)。これでお終いです。すごいですね。
上のキモを例示しておきます。
a = ->(s, t){t + s}.curry ["ruby", "violin"].map &a.call("ist") #=>["rubyist", "violinist"]
こういうことのできる Ruby は本当におもしろい言語ですね。わかってみればとても美しいではないか。それにしても、ブロックという言語仕様は Matz が気に入っていると云うとおり、味のあるやつですなあ。
※追記
なお、元のコードで map() となっている括弧は、引数を括っているわけではないと思います。map は引数を取らないので。ただブロックをラップしているだけなのではないでしょうか。いや、[1, 2, 3].map({|i| i ** 2})
がエラーになるからちがうかな。どうなのでしょう。ちなみに、%w(perl python ruby).map(&:upcase)
の map と後の括弧の間に空白を入れるとエラーになるから、特殊な構文なのですかね。