Symbol#to_proc

http://wota.jp/ac/?date=20060309#p01
すごい今更〜なネタですが,知らなかった&感動したのでメモ.

class Symbol #:nodoc:
  def to_proc
    Proc.new { |obj, *args| obj.send(self, *args) }
  end
end

こうすると,

>> p [1,2,3].map { |e| e.succ }
[2, 3, 4]
>> p [1,2,3].map { |e| e.to_s }
["1", "2", "3"]

こういう事が

>> p [1,2,3].map(&:succ)
[2, 3, 4]
>> p [1,2,3].map(&:to_s)
["1", "2", "3"]

こういう風にできる.素敵.


なんかもう,何が起こってるのか意味わかんなくなる.なんでmapにカッコがあって,Symbolに&が付いてんだよ,とか.


まず,mapに括弧がついている件.
多分,mapに何かを渡すと,その何かがto_procされると予想.

>> p([1,2,3].map(&proc{|e| e.to_s}))
["1", "2", "3"]

うんうん,それっぽいぞ.


さらに,&について.リファレンスマニュアルを読んでみると,

ブロックの部分だけを先に定義して変数に保存しておき、後からブロック付きメソッドに渡すことも出来ます。それを実現するのが手続きオブジェクト(Proc)です。それをブロックとして渡すにはブロック付きメソッドの最後の引数として `&' で修飾した手続きオブジェクトを渡します。
プログラミング言語 Ruby リファレンスマニュアルより

とのことで,mapはブロック付メソッドであり,引数はブロックしか取らないので,上のような形になったんですねぇ.
もうすこし引用すると,

version 1.7 では、to_proc メソッドを持つオブジェクトならば、`&' 修飾した引数として渡すことができます(デフォルトで Proc、Method オブジェクトは共に to_proc メソッドを持ちます)。to_proc はメソッド呼び出し時に実行され、Proc オブジェクトを返すことが期待されます。
プログラミング言語 Ruby リファレンスマニュアルより

なるほど,これでSymbolクラスにto_procメソッドを定義した意味が分かりましたね.


ひねくれた人はココで「SymbolじゃなくてStringでも行けるじゃん」って突っ込むかもしれませんが,まぁ確かに行ける,しかしそこは美学の問題だろう,という結論が脳内で出ました.

少しひねくれる

ちょっとまて,上の例では*argsって何も使ってないじゃないかっ.と言うよりむしろ引数は渡せない前提じゃん.ならこれで良いだろう.

class Symbol
  def to_proc
    Proc.new{|obj| obj.send(self)}
  end
end


ここで,汚いながらも無理矢理引数を渡せるように画策.

class Symbol #:nodoc:
  def [] *args
    self.to_proc(*args)
  end
  
  def to_proc(*args)
    Proc.new { |obj| obj.send(self, *args) end
  end
end

p [1,2,3].map(&:"+"[1])
=>[2, 3, 4]
p [[1,2,3],[2,3,4],[3,4,5]].map{|e| e.map(&:succ)}
=>[[2, 3, 4], [3, 4, 5], [4, 5, 6]]

できる限りの可読性を確保したつもり(実行時の).引数を渡すためにメソッド(?)を増やすのは,致し方ないか?


夢のmapmapを実現したい…

p [[1,2,3],[2,3,4],[3,4,5]].map(&:map[(&:succ)])

なんてことはできない.


エラーはsuccの前の&で起こっているのだけど,mapやeachが,&が無いとProcを渡せないっていう仕様に難があるんだな.そもそも,こんなのちゃんと構文解析できないし.


まぁこういう際いことがしたかったら普通に書け,と.