2006年08月03日
_ Rubyの深淵
いままでためこんだRubyのevalだのリフレクションだのの 知識をまとめようかなーと思っているんで すがいまいち気力がわきません。
そこでここに適当に垂れ流していこうと思います。
最初はブロックの話でも書きます。
_ 手続きオブジェクトとブロック
手続きオブジェクト(Procクラスのインスタンス)の持つ機能は いわゆるクロージャ、無名関数、lambdaと呼ばれるようなものです。 で、ブロックはこれのシンタックスシュガーと言えます。 細かいことを言うと単なるシンタックスシュガーではない点がある のですが、それは過去との互換性のためのようです。
Rubyの特徴の一つであるブロックの優れた点は見た目が優れている 所です。つまり手続き型系統の言語に見た目にうまくなじむという点です。
ブロックの一般的な使いかたは <URL:http://i.loveruby.net/w/RubyIteratorPattern.html> を見てください。
ブロックと手続きオブジェクトの相互変換
まずは手続きオブジェクトをブロックに変換します。 これはブロックとして渡したいオブジェクトを & で修飾して メソッド引数の最後に置けばよいです。つまり、
block = proc{ … } foo.bar(1, 2, &block)
とします。 逆にメソッドに渡されたブロックを手続きオブジェクトに変換することもできます。 これは、メソッド定義の仮引数の最後の変数に&を付ければよいのです。 つまり、
def foo(a, b, &block) : end
とした場合、
a.foo(1,3){|x| x+1}
とすると、変数blockにproc{|x| x+1}が渡されます。 ブロックを付けずにメソッドを呼びだした場合blockにはnilが代入されます。
例
これらの機能は、様々な面で役にたちます。特にメタ機能的なものを作るときに おおいに役に立つのですが、ここではもっと簡単な例を挙げます。
まずは、ブロックをほかのメソッドに転送する場合です。 Companyクラスは@employeesインスタンスを持っていて、Companyに each_employeeというメソッドを持たせたいとします。 このときは、
class Company def initialize @employees = … end def each_employee(&block) @employees.each(&block) end end
とします。
次に、無限リストを作りましょう。
class Pair attr_reader :car def initialize(car, &cdrblock) @car = car @cdrblock = cdrblock @cdr = nil @cdr_initialized = false end def cdr unless @cdr_initialized @cdr = @cdrblock.call @cdr_initialized = true end return @cdr end def take(n) ary = [] list = self n.times{ break if list == :term; ary << list.car; list = list.cdr } return ary end end
ついでにzip_withも作ります。
def zip_with(xs, ys, &block) return :term if xs == :term || ys == :term Pair.new( block[xs.car, ys.car]){zip_with(xs.cdr, ys.cdr, &block)} end
すると、フィボナッチ数列は以下のようになります。
fib = Pair.new(1){ Pair.new(1){ zip_with(fib, fib.cdr){|x,y| x+y} } } p fib.take(10)
こんなこともできます。
def cons(car, &cdr) Pair.new(car, &cdr) end fib2 = cons(1){ cons(1){ zip_with(fib2, fib2.cdr){|x,y| x+y}}}
このような芸当は yield だけでは無理でしょう。 また、ブロックの一つの効用として、遅延評価ができるというのもある わけです。
次回はsendとMethodクラスの話でも書きます。