2012-01-29 7 views
80

が必要なのでしょうか?私はは、なぜ我々は古典的な例を持っている繊維用繊維

def clsr 
    x, y = 0, 1 
    Proc.new do 
    x, y = y, x + y 
    x 
    end 
end 

ので

10.times { puts fib.resume } 

prc = clsr 
10.times { puts prc.call } 

はちょうど同じ結果を返します(実際には、閉鎖)ちょうど同じPROCでこれを書き換えることができます。

繊維の利点は何ですか? lambdaや他のクールなRubyの機能ではできないFibersでどんなものを書くことができますか?

+4

古いフィボナッチの例は、もっとも最悪のモチベーターにすぎません;-) O(1)の_any_フィボナッチ数を計算するために使用できる数式もあります。 – usr

+15

問題はアルゴリズムではなく、ファイバーを理解することです。 – fl00r

答えて

197

ファイバーは、おそらくアプリケーションレベルのコードで直接使用することのないものです。これらはフロー制御プリミティブで、他の抽象クラスを構築するために使用できます。抽象クラスは、上位レベルのコードで使用します。

Rubyで#1の繊維を使用するのは、Ruby 1.9のコアRubyクラスであるEnumeratorを実装することでしょう。これらは、信じられないほどです。

Ruby 1.9では、のほとんどすべての反復子メソッドをなしで呼び出すと、Enumeratorが返されます。

irb(main):001:0> [1,2,3].reverse_each 
=> #<Enumerator: [1, 2, 3]:reverse_each> 
irb(main):002:0> "abc".chars 
=> #<Enumerator: "abc":chars> 
irb(main):003:0> 1.upto(10) 
=> #<Enumerator: 1:upto(10)> 

これらEnumerator sが列挙オブジェクトであり、そのeach方法は、ブロックと呼ばれていた、元のイテレータ方法により得られたであろう要素をもたらします。私がちょうど与えた例では、reverse_eachによって返された列挙子は、eachメソッドを持ち、3,2,1を返します。 charsによって返された列挙子は、 "c"、 "b"、 "a"(など)を返します。

irb(main):001:0> e = "abc".chars 
=> #<Enumerator: "abc":chars> 
irb(main):002:0> e.next 
=> "a" 
irb(main):003:0> e.next 
=> "b" 
irb(main):004:0> e.next 
=> "c" 

あなたが「内部イテレータ」と「外部イテレータ」(良いと聞いたことがあります。あなたが繰り返し、それにnextを呼び出す場合は、オリジナルのiteratorメソッドとは異なり、列挙子も1によって要素の1を返すことができます両方の説明は "Gang of Four" Design Patternsの本に記載されています)。上記の例は、列挙子を使用して内部イテレータを外部イテレータに変換することができることを示しています。

これは、独自の列挙子を作るための一つの方法である:

class SomeClass 
    def an_iterator 
    # note the 'return enum_for...' pattern; it's very useful 
    # enum_for is an Object method 
    # so even for iterators which don't return an Enumerator when called 
    # with no block, you can easily get one by calling 'enum_for' 
    return enum_for(:an_iterator) if not block_given? 
    yield 1 
    yield 2 
    yield 3 
    end 
end 

はのは、それを試してみましょう:

e = SomeClass.new.an_iterator 
e.next # => 1 
e.next # => 2 
e.next # => 3 

は、ちょっと待って...何が奇妙に思えるのでしょうか? yieldステートメントは、直線コードとしてan_iteratorに書かれていますが、列挙子はに1つずつを実行できます。 nextへの呼び出しの間に、an_iteratorの実行が「フリーズ」されます。 nextに電話をかけるたびに、次のyieldステートメントまで実行を続けてから、再度「フリーズ」します。

これはどのように実装されていますか?列挙子は、an_iteratorへの呼び出しをファイバーにラップし、ファイバーを中断するブロックを渡します。したがって、ブロックにan_iteratorが発生するたびに、実行中のファイバは中断され、メインスレッド上で実行が継続されます。次回にnextと呼ぶと、制御がファイバに渡され、ブロックがを返し、an_iteratorが中断したところで続きます。

繊維なしでこれを行うために必要なことを考えることは有益です。内部イテレータと外部イテレータの両方を提供したいすべてのクラスは、nextへの呼び出しの間の状態を追跡するための明示的なコードを格納する必要があります。 nextへの各呼び出しは、その状態をチェックし、値を返す前にそれを更新する必要があります。ファイバーを使用すると、が自動的にの内部イテレーターを外部イテレーターに変換できます。

これはファイバーのpersayとは関係ありませんが、Enumeratorsでできることをもう1つ言いましょう:上位のEnumerableメソッドをeach以外の他のイテレーターに適用することができます。それについて考えてみましょう:mapselectinclude?injectなどを含むすべてのEnumerableメソッド、すべてeachによって生成される要素で動作します。しかし、オブジェクトにeach以外のイテレータがある場合はどうなりますか?なしブロックとイテレータを呼び出す

irb(main):001:0> "Hello".chars.select { |c| c =~ /[A-Z]/ } 
=> ["H"] 
irb(main):002:0> "Hello".bytes.sort 
=> [72, 101, 108, 108, 111] 

は列挙子を返し、その後、あなたはその上で、他の列挙メソッドを呼び出すことができます。

繊維に戻って、Enumerableのtakeメソッドを使用しましたか?

class InfiniteSeries 
    include Enumerable 
    def each 
    i = 0 
    loop { yield(i += 1) } 
    end 
end 

何がeach方法は、それはそれは返すべきではありませんようになっていることを呼び出した場合は、右?これをチェックしてください:

InfiniteSeries.new.take(10) # => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 

これはフードの下で繊維を使用しているかどうかわかりませんが、それは可能です。無限リストとシリーズの遅延評価を実装するために、ファイバーを使用することができます。列挙子で定義されたいくつかの遅延メソッドの例については、ここでいくつか定義しました。https://github.com/alexdowad/showcase/blob/master/ruby-core/collections.rb

ファイバーを使用して汎用コルーチン機能を構築することもできます。私はまだ自分のプログラムの中でコルーチンを使ったことは一度もありませんでしたが、それは良い考え方です。

私はこれがあなたに可能性の考えを与えることを願っています。最初に述べたように、ファイバーは低レベルのフロー制御プリミティブです。プログラム内で複数のコントロールフローの位置(ブックのページの異なる「ブックマーク」など)を維持し、必要に応じてそれらの間を切り替えることが可能です。任意のコードはファイバーで実行できるので、ファイバー上でサードパーティーのコードを呼び出し、それを「フリーズ」し、制御しているコードをコールバックするときに何かを続けることができます。

このようなイメージを想像してみてください。多くのクライアントにサービスを提供するサーバープログラムを作成しています。クライアントとの完全な対話には一連の手順が必要ですが、各接続は一時的なものであり、接続間の各クライアントの状態を覚えておく必要があります。

明示的にその状態を保存し、クライアントが接続するたびにチェックして(次のステップが何であるかを確認するのではなく)、クライアントごとにファイバを維持することができます。クライアントを特定したら、ファイバーを取り出して再起動します。その後、各接続の終わりに、ファイバーを一時停止して再び保管します。このようにして、すべてのステップを含む完全な相互作用のためのすべてのロジックを実装するためのコードを書くことができます(プログラムがローカルで実行された場合と同じように)。

このようなことが実用的でない(少なくとも今のところ)理由はたくさんあると思いますが、やはり私はあなたにいくつかの可能性を示しています。知るか;一度あなたがコンセプトを取得したら、誰もまだ考えていないまったく新しいアプリケーションを考え出すことができます!

+0

お返事ありがとうございました!だからなぜ彼らは 'chars'や他の列挙子をクロージャーだけで実装していないのですか? – fl00r

+0

@ fl00r、さらに情報を追加するつもりですが、この回答が既に長すぎるかどうかわかりません...もっと欲しいですか? –

+0

私は欲しい! :)大きな喜びで! – fl00r

17

定義された入口および出口点を有するクロージャとは異なり、繊維はそれらの状態およびリターン(収率)を保持することができ、何度:

f = Fiber.new do 
    puts 'some code' 
    param = Fiber.yield 'return' # sent parameter, received parameter 
    puts "received param: #{param}" 
    Fiber.yield #nothing sent, nothing received 
    puts 'etc' 
end 

puts f.resume 
f.resume 'param' 
f.resume 

プリントこれ:この

some code 
return 
received param: param 
etc 

実装他のルビ機能との論理はあまり読みにくくなります。

この機能を使用すると、手動での協調スケジューリング(スレッド交換)を行うことが、ファイバの使用効率を上げることになります。 Ilya Grigorikは、非同期実行のIOスケジューリングの利点を失うことなく、非同期ライブラリ(この場合はeventmachine)を同期APIのように見えるようにする良い例を持っています。ここにはlinkがあります。

+0

ありがとう!私はドキュメントを読んでいるので、私は繊維の中に多くの入り口と出口を持つこの魔法をすべて理解しています。しかし、私はこのようなものが人生を楽にするとは確信していません。私はこのすべての履歴書と利回りに従うことをお勧めしているとは思わない。それは解くのが難しい手のひらのように見えます。だから私は、この繊維の裂け目が良い解決策である場合があるかどうかを理解したい。 Eventmachineはクールですが、繊維を理解するのに最適な場所ではありません。なぜなら、最初にこのすべての原子炉パターンのことを理解する必要があるからです。だから、私はもっと単純な例で繊維の「物理的な意味」を理解することができると信じています – fl00r

関連する問題