トップ «前の日記(2006年07月30日) 最新 次の日記(2006年08月04日)» 編集

ohai日誌

2003|12|
2004|01|02|03|04|05|06|07|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|06|08|10|11|
2008|01|02|03|04|05|07|09|
2009|01|02|
2013|06|12|
2014|01|02|03|04|06|09|10|12|
2015|04|
2016|09|
2018|02|

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クラスの話でも書きます。


トップ «前の日記(2006年07月30日) 最新 次の日記(2006年08月04日)» 編集