ファイバーは、アプリケーションレベルのコードで直接使用することはおそらくないでしょう。これらはフロー制御プリミティブであり、他の抽象化を構築するために使用でき、それを高レベルのコードで使用できます。
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_iterator
next
an_iterator
繊維なしでこれを行うために何が必要になるかを考えることは有益でしょう。内部イテレータと外部イテレータの両方を提供したいすべてのクラスには、への呼び出し間の状態を追跡するための明示的なコードが含まれている必要がありますnext
。nextを呼び出すたびに、その状態を確認し、値を返す前に更新する必要があります。ファイバーを使用すると、内部イテレーターを外部イテレーターに自動的に変換できます。
これはファイバーの説明とは関係ありませんが、列挙子を使用して実行できるもう1つのことを説明します。これにより、。以外の他の反復子に高階のEnumerableメソッドを適用できますeach
。考えてみてください。通常、、、、、などを含むすべてのEnumerableメソッドは、によって生成map
さselect
れた要素に対して機能します。しかし、オブジェクトに他のイテレータがない場合はどうなりますか?include?
inject
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
Enumerableのメソッドを使用しましたか?
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プログラミングのように聞こえますか?)
その状態を明示的に保存し、クライアントが接続するたびにそれをチェックする(クライアントが実行する必要のある次の「ステップ」を確認する)のではなく、クライアントごとにファイバーを維持できます。クライアントを識別した後、ファイバーを取得して再起動します。次に、各接続の最後に、ファイバーを一時停止して再度保管します。このようにして、すべてのステップを含む完全な相互作用のためのすべてのロジックを実装するための直線的なコードを書くことができます(プログラムがローカルで実行されるようにされた場合と同じように)。
そのようなことが実用的でない理由はたくさんあると思いますが(少なくとも今のところ)、ここでもいくつかの可能性を紹介しようとしています。知るか; コンセプトを理解すると、他の誰もまだ考えていないまったく新しいアプリケーションを思いつくかもしれません。