7

Rubyのドキュメントによると、またはメソッドeachにターゲットメソッドが提供されていない場合、Enumeratorオブジェクトはメソッドを(列挙するために)使用します。それでは、例として、次のモンキーパッチとその列挙子を取り上げましょう。to_enumenum_for

o = Object.new
def o.each
    yield 1
    yield 2
    yield 3
end
e = o.to_enum

loop do
  puts e.next
end

Enumeratorオブジェクトがeachメソッドを使用してが呼び出されたときに応答するnextとすると、メソッドの呼び出しは、呼び出されるeachたびにどのようになりnextますか?Enumeartorクラスは、のすべてのコンテンツをプリロードし、o.each列挙用のローカルコピーを作成しますか?nextまたは、enumeartorで呼び出されるまで、各yieldステートメントで操作をハングさせるある種のRubyマジックはありますか?

内部コピーが作成された場合、それはディープコピーですか?外部列挙に使用できるI/Oオブジェクトはどうですか?

Ruby1.9.2を使用しています。

4

1 に答える 1

11

魔法ではありませんが、それでも美しいです。ある種のコピーを作成する代わりに、aFiberを使用して最初eachにターゲットの列挙可能なオブジェクトを実行します。の次のオブジェクトを受け取った後each、はこのオブジェクトを生成し、それによって制御を最初に再開さFiberれた場所に戻します。Fiber

このアプローチでは、たとえば列挙可能オブジェクトを呼び出すことによって取得することを想像できるように、列挙可能オブジェクトのコピーまたは他の形式の「バックアップ」を必要としないため、これは美しいことです#to_a。ファイバーを使用した協調スケジューリングにより、何らかの形で先読みをすることなく、必要なときに正確にコンテキストを切り替えることができます。

それはすべて、のCコードで発生しEnumeratorます。ほぼ同じ動作を示す純粋なRubyバージョンは、次のようになります。

class MyEnumerator
  def initialize(enumerable)
    @fiber = Fiber.new do
      enumerable.each { |item| Fiber.yield item }
    end
  end

  def next
    @fiber.resume || raise(StopIteration.new("iteration reached an end"))
  end
end

class MyEnumerable
  def each
    yield 1
    yield 2
    yield 3
  end
end

e = MyEnumerator.new(MyEnumerable.new)
puts e.next # => 1
puts e.next # => 2
puts e.next # => 3
puts e.next # => StopIteration is raised
于 2012-06-15T21:14:01.427 に答える