2009-09-16 9 views
7

この質問は、Ruby 1.9.1で列挙子を使用する方法ではなく、どのように動作するのか不思議です。ここではいくつかのコードは次のとおりです。Ruby 1.9.1で列挙子はどのように機能しますか?

上記のコードで
class Bunk 
    def initialize 
    @h = [*1..100] 
    end 

    def each 
    if !block_given? 
     enum_for(:each) 
    else 
     0.upto(@h.length) { |i| 
     yield @h[i] 
     } 
    end 
    end 
end 

私は、連続する各要素を取得しe = Bunk.new.each、その後、e.nexte.nextを使用することができますが、正確にどのようにそれが実行を停止し、その後、適切な場所で再開されますか?

私は、0.uptoの収率がFiber.yieldに置き換えられた場合、理解しやすいことを認識していますが、ここには該当しません。それは普通の古いyieldなので、どうやって動くのですか?

私はenumerator.cを見ましたが、私にとっては理解できない部分です。多分、誰かが、1.8.6スタイルの継続ベースの列挙子ではなく、ファイバーを使ってRubyで実装することができます。

答えて

12

ここで繊維を使用し、ほとんど元のように振る舞うべき平野ルビー列挙子です:

class MyEnumerator 
    include Enumerable 

    def initialize(obj, iterator_method) 
    @f = Fiber.new do 
     obj.send(iterator_method) do |*args| 
     Fiber.yield(*args) 
     end 
     raise StopIteration 
    end 
    end 

    def next 
    @f.resume 
    end 

    def each 
    loop do 
     yield self.next 
    end 
    rescue StopIteration 
    self 
    end 
end 

そして誰かが、フロー制御などの例外文句を言う前に:本当の列挙子はそう、あまりにも、最後に呼び出すとStopIterationを発生させます私は元の動作をエミュレートしました。

使用法:

>> enum = MyEnumerator.new([1,2,3,4], :each_with_index) 
=> #<MyEnumerator:0x9d184f0 @f=#<Fiber:0x9d184dc> 
>> enum.next 
=> [1, 0] 
>> enum.next 
=> [2, 1] 
>> enum.to_a 
=> [[3, 2], [4, 3]] 
4

実際にあなたのe = Bunk.new.eachではelse節は最初に実行されません。代わりに 'if!block_given'節が実行され、列挙子オブジェクトが返されます。列挙子オブジェクトはファイバーオブジェクトを内部的に保持します。 (少なくともenumerator.cのようなものです)

e.eachを呼び出すと、ファイバーを内部的に使用してその実行コンテキストを追跡する列挙子のメソッドを呼び出しています。このメソッドは、ファイバー実行コンテキストを使用してBunk.eachメソッドを呼び出します。ここでBunk.eachを呼び出すと、else節が実行され、値が返されます。

歩留まりがどのように実装されているか、ファイバが実行コンテキストをどのように追跡しているかはわかりません。私はそのコードを見ていない。列挙子と繊維の魔法のほとんどはC言語で実装されています。

繊維と収量の実装方法は本当ですか?どのレベルの詳細をお探しですか?

私がオフの場合は、私を修正してください。

+0

あなたの答えはありがたいです。はい、これについてかなり多くの詳細を求めています。具体的には、Rubyですべてを実装することが可能かどうか、またはRubyでは不可能なCで何か不機嫌なことが起こっているかどうかを知りたいと思います。純粋にRubyで実装できるのであれば、コードを見たいと思っています! :) – horseyguy

1

他のポスターが指摘したように、私はそれが[1.9]独自のプライベート "繊維" を作成します信じています。 1.8.3(または1.8.6ではバックポートの宝石を使用している場合)何とか同じことをします(おそらく、1.8のすべてのスレッドがファイバーに相当するので、単にそれらを使用しているからです)。

したがって1.9そして、は1.8.x、あなたはそれらのいくつかを一緒に a.each_line.map.each_with_index {}

実際に種類のコマンドラインのパイプのように、各ラインでその全体のチェーンを通って流れるの連鎖場合

http://pragdave.blogs.pragprog.com/pragdave/2007/12/pipelines-using.html

HTH。

+1

ここにはすばらしい詳細な説明があります http://wiki.github.com/rdp/ruby_tutorials_core/enumerator – rogerdpack

1

これはもっと正確だと思います。列挙子のそれぞれを呼び出すことは、元の反復子メソッドを呼び出すことと同じでなければなりません。だから私はこれに元の解決策を少し変更するでしょう:

class MyEnumerator 
    include Enumerable 

    def initialize(obj, iterator_method) 
    @f = Fiber.new do 
     @result = obj.send(iterator_method) do |*args| 
     Fiber.yield(*args) 
     end 
     raise StopIteration 
    end 
    end 

    def next(result) 
    @f.resume result 
    end 

    def each 
    result = nil 
    loop do 
     result = yield(self.next(result)) 
    end 
    @result 
    end 
end 
関連する問題