ファイバーは、おそらくアプリケーションレベルのコードで直接使用することのないものです。これらはフロー制御プリミティブで、他の抽象クラスを構築するために使用できます。抽象クラスは、上位レベルのコードで使用します。
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
以外の他のイテレーターに適用することができます。それについて考えてみましょう:map
、select
、include?
、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
ファイバーを使用して汎用コルーチン機能を構築することもできます。私はまだ自分のプログラムの中でコルーチンを使ったことは一度もありませんでしたが、それは良い考え方です。
私はこれがあなたに可能性の考えを与えることを願っています。最初に述べたように、ファイバーは低レベルのフロー制御プリミティブです。プログラム内で複数のコントロールフローの位置(ブックのページの異なる「ブックマーク」など)を維持し、必要に応じてそれらの間を切り替えることが可能です。任意のコードはファイバーで実行できるので、ファイバー上でサードパーティーのコードを呼び出し、それを「フリーズ」し、制御しているコードをコールバックするときに何かを続けることができます。
このようなイメージを想像してみてください。多くのクライアントにサービスを提供するサーバープログラムを作成しています。クライアントとの完全な対話には一連の手順が必要ですが、各接続は一時的なものであり、接続間の各クライアントの状態を覚えておく必要があります。
明示的にその状態を保存し、クライアントが接続するたびにチェックして(次のステップが何であるかを確認するのではなく)、クライアントごとにファイバを維持することができます。クライアントを特定したら、ファイバーを取り出して再起動します。その後、各接続の終わりに、ファイバーを一時停止して再び保管します。このようにして、すべてのステップを含む完全な相互作用のためのすべてのロジックを実装するためのコードを書くことができます(プログラムがローカルで実行された場合と同じように)。
このようなことが実用的でない(少なくとも今のところ)理由はたくさんあると思いますが、やはり私はあなたにいくつかの可能性を示しています。知るか;一度あなたがコンセプトを取得したら、誰もまだ考えていないまったく新しいアプリケーションを考え出すことができます!
古いフィボナッチの例は、もっとも最悪のモチベーターにすぎません;-) O(1)の_any_フィボナッチ数を計算するために使用できる数式もあります。 – usr
問題はアルゴリズムではなく、ファイバーを理解することです。 – fl00r