ファイバーは、アプリケーションレベルのコードで直接使用することはおそらくないでしょう。これらはフロー制御プリミティブであり、これを使用して他の抽象化を構築し、それを上位レベルのコードで使用できます。
おそらく、Rubyでのファイバーの最大の使用法はEnumerator
、Ruby 1.9のコアRubyクラスであるを実装することです。これらは非常に便利です。
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
のはEnumerableオブジェクトであり、それらのeach
メソッドは、ブロックで呼び出された場合、元のイテレーターメソッドによって生成されたはずの要素を生成します。先ほどの例では、によって返されるEnumeratorにreverse_each
は、each
3、2、1を生成するメソッドがあります。によって返される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 of Four」デザインパターンの本に両方の説明が記載されています)。上記の例は、列挙子を使用して内部イテレーターを外部イテレーターに変換できることを示しています。
これは、独自の列挙子を作成する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
直線的コードとしてではなく、列挙子は、それらを実行することができ一つずつ。の呼び出しの合間にnext
、の実行an_iterator
は「凍結」されます。を呼び出すたびnext
に、次のyield
ステートメントまで実行され続け、再び「フリーズ」します。
これがどのように実装されているか推測できますか?Enumeratorはへの呼び出しをan_iterator
ファイバーでラップし、ファイバーを中断するブロックを渡します。したがってan_iterator
、ブロックに譲るたびに、それが実行されているファイバーは一時停止され、実行はメインスレッドで続行されます。次にを呼び出すとnext
、制御がファイバーに渡され、ブロックは戻り、an_iterator
中断したところから続行します。
繊維なしでこれを行うには何が必要かを考えると参考になります。内部イテレータと外部イテレータの両方を提供したいすべてのクラスには、への呼び出し間の状態を追跡するための明示的なコードを含める必要がありますnext
。nextを呼び出すたびに、その状態を確認し、値を返す前に更新する必要があります。ファイバーを使用すると、内部イテレーターを外部イテレーターに自動的に変換できます。
これはファイバーパーセーとは関係ありませんが、列挙子で実行できるもう1つのことを述べます。これにより、高次列挙型メソッドを以外の他の反復子に適用できますeach
。それについて考える:通常、すべての列挙メソッドは、含む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プログラミングのように聞こえますか?)
その状態を明示的に保存し、クライアントが接続するたびに状態をチェックする(クライアントが実行する必要のある次の「ステップ」を確認する)のではなく、各クライアントのファイバーを維持できます。クライアントを識別したら、ファイバーを取得して再起動します。次に、各接続の最後に、ファイバーを一時停止して再び保管します。このようにして、直線的なコードを記述して、すべての手順を含む完全な対話のためのすべてのロジックを実装できます(プログラムがローカルで実行されるようにした場合と同様に)。
(少なくとも今のところ)そのようなことが実用的でない理由はたくさんあると思いますが、ここでも、いくつかの可能性を紹介しようとしています。知るか; コンセプトを理解したら、まだ誰も考えていないまったく新しいアプリケーションを思いつくかもしれません!