102

ファイバーの場合、典型的な例があります。フィボナッチ数の生成です。

fib = Fiber.new do  
  x, y = 0, 1 
  loop do  
    Fiber.yield y 
    x,y = y,x+y 
  end 
end

なぜここにファイバーが必要なのですか?同じProcでこれを書き直すことができます(実際にはクロージャー)

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 }

まったく同じ結果が返されます。

では、繊維の利点は何ですか。ラムダやその他のクールなRuby関数ではできない、Fibersで書くことができるものは何ですか?

4

2 に答える 2

230

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

Enumeratorおそらく、Rubyでのファイバーの最大の用途は、Ruby1.9のコアRubyクラスであるsを実装することです。これらは非常に便利です。

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は列挙可能なオブジェクトであり、それらのeachメソッドは、ブロックで呼び出された場合に元のイテレータメソッドによって生成されたであろう要素を生成します。先ほど示した例では、によって返される列挙子には、 3,2,1を生成reverse_eachするメソッドがあります。eachによって返される列挙子は、chars"c"、 "b"、 "a"(など)を生成します。nextただし、元のイテレータメソッドとは異なり、列挙子を繰り返し呼び出すと、列挙子は要素を1つずつ返すこともできます。

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"

「内部イテレータ」と「外部イテレータ」について聞いたことがあるかもしれません(両方の適切な説明は、「Gang ofFour」デザインパターンの本に記載されています)。上記の例は、列挙子を使用して内部反復子を外部反復子に変換できることを示しています。

これは、独自の列挙子を作成する1つの方法です。

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_iteratornextan_iterator

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

これはファイバーの説明とは関係ありませんが、列挙子を使用して実行できるもう1つのことを説明します。これにより、。以外の他の反復子に高階のEnumerableメソッドを適用できますeach。考えてみてください。通常、、、、、などを含むすべてのEnumerableメソッドは、によって生成mapselectれた要素に対して機能します。しかし、オブジェクトに他のイテレータがない場合はどうなりますか?include?injecteacheach

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メソッドを呼び出すことができます。

ファイバーに戻って、takeEnumerableのメソッドを使用しましたか?

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

ファイバーを使用して汎用コルーチン施設を構築することもできます。私はまだどのプログラムでもコルーチンを使用したことがありませんが、知っておくとよい概念です。

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

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

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

そのようなことが実用的でない理由はたくさんあると思いますが(少なくとも今のところ)、ここでもいくつかの可能性を紹介しようとしています。知るか; コンセプトを理解すると、他の誰もまだ考えていないまったく新しいアプリケーションを思いつくかもしれません。

于 2012-02-08T13:17:08.473 に答える
22

入口と出口が定義されているクロージャーとは異なり、ファイバーはその状態を維持し、何度も戻る(歩留まり)ことができます。

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

他のruby機能を使用したこのロジックの実装は、読みにくくなります。

この機能を使用すると、ファイバーの適切な使用法は、手動の協調スケジューリングを実行することです(スレッドの置き換えとして)。Ilya Grigorikは、非同期ライブラリ(eventmachineこの場合)を非同期実行のIOスケジューリングの利点を失うことなく、同期APIのように見えるようにする方法についての良い例を示しています。ここにリンクがあります。

于 2012-01-29T12:39:48.770 に答える