やはりRubyでは 〜Scalaの無名関数に憧れて〜
Posted by nanki Wed, 28 Oct 2009 21:18:00 GMT
憧れシリーズ第二弾。
Scalaの勉強でもしようかな、と思ってScalaの本を開いたら、Scalaでは無名関数を_(アンダースコア)を使って定義できるらしい。 Rubyにおけるそれの実現可能性に考えが行ってしまって、Scalaの本を読むのはそれでおしまいになったのだ…
Rubyでも、
[1, 2, 3].map(_ * 2) # => [2, 4, 6]とか書きたいよね。
Rubyにはもともと、mapなどのブロックを取るメソッドに無名関数(Proc)を引数として渡すことができる
[1, 2, 3].map(&:to_s)この例では、:to_s は Symbol だが、Proc を返すSymbol#to_proc を定義しておくと、それが呼ばれてよしなにしてくれる。Symbol#to_proc はActiveSupport などで定義されているので、Rails使いにはおなじみだろう。
この仕組みを利用して、 _ に対して送られるメッセージを保存しておいて、to_proc される時に適当なProcを返すような何かを作ればよい。
module Scala
class LazyCall
instance_methods.each do |v|
undef_method(v) unless %w(__id__ __send__).include?(v)
end
def initialize(v = nil)
@v = v
@msgs = []
end
def method_missing(*args, &block)
@msgs << [args, block] unless args == [:respond_to?, :to_proc]
self
end
def to_proc
proc do |v|
@msgs.inject(@v||v) do |r, m|
r.__send__(*m[0].map{|a|LazyCall === a ? a.to_proc[v] : a}, &m[1])
end
end
end
end
def _(v = nil)
LazyCall.new v
end
# _0, _1, ...
(0...3).each do |i|
define_method("_#{i}"){LazyCall.new[i]}
end
end
include Scalaそうしてできたのがこれ。
これを使うと…
%w(a b c d).map(&_ * _.to_i(16))
# => ["aaaaaaaaaa", "bbbbbbbbbbb", "cccccccccccc", "ddddddddddddd"]
[[1, 2], [2, 3]].map(&_0 + _1)
# => [3, 5]
_(Math).sqrt(_0 ** 2 + _1 ** 2).to_proc[[3, 4]]
# => 5.0& の後に括弧が要ると思っていたがそんなことはなかったぜ。
メッセージをフックする関係上、直接 Math.sqrt(_0 ** 2 + _1 ** 2) とは書けないけど、そう書きたい人はブロック使ってください。
以下簡単な説明
- _ は
LazyCallのインスタンスを返し、受け取ったメッセージを引数と共に保存。 [:respond_to?, :to_proc]をはじいているのは、引数として渡される時にこの問い合わせが発生するので、それを無視するため。(副作用として、無名関数では.respond_to? :to_procが使えなくなると思う)to_proc内で、保存したメッセージを、procの引数に送り直している。- 保存された引数の中に、LazyCallのインスタンスがある場合は、call してその結果と置き換えている。これによって、
_ * _のような表記が可能になる。 _0は、_.[0]のエイリアス
二夜にわたるProxyObject特集、いかがでしたでしょうか。さて、来週からは…
追記:2009/10/30
Scalaの _ (プレースホルダ構文と呼ぶらしい) は _ + _と書いたとき、二つの _ が同じものを意味しないそうです。
と、閉じた本の続きに書いてあった。
あと、_0 などを使うと、20倍くらい遅い。どうしたものか。
