Melborne さんの華麗な技巧を鑑賞する

落ちていく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 と後の括弧の間に空白を入れるとエラーになるから、特殊な構文なのですかね。