[解決済み] なぜ、繊維が必要なのか
疑問点
ファイバーについては、フィボナッチ数の生成という古典的な例があります。
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の機能ではできない、ファイバーを使ったどんなことが書けるのでしょうか?
どうやって解決するの?
ファイバーは、おそらくアプリケーションレベルのコードで直接使用することはないでしょう。これはフロー制御のためのプリミティブで、他の抽象化を構築するために使用でき、その後より高レベルのコードで使用します。
Rubyでファイバーを使う一番の方法は、おそらく
Enumerator
を実装することです。これは Ruby 1.9 のコアクラスです。これらは
信じられないほど
であり、非常に便利です。
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
メソッドは、元のイテレータメソッドがブロック付きで呼び出された場合に 得られるはずだった要素を得ます。先程の例では
reverse_each
が返す Enumerator には
each
メソッドがあり、3,2,1 を返します。が返すEnumeratorは
chars
は "c","b","a" (といった具合に) を返します。しかし、本来のイテレータメソッドとは異なり、Enumeratorは、次のように呼び出すと、要素を一つずつ返すこともできます。
next
を繰り返し呼び出すと、要素を一つずつ返すことができます。
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" Design Patterns」の本に記載されています)。上記の例では、Enumerator を使用して内部イテレータを外部イテレータに変えることができることを示しています。
これは、独自の列挙子を作るための一つの方法です。
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
を直線的なコードとして実行することができますが、Enumeratorはそれらを
を一度に一つずつ
. の呼び出しの間に
next
の実行は
an_iterator
は "frozen"である。呼び出すたびに
next
を呼び出すたびに、以下のように実行され続けます。
yield
ステートメントまで実行し続け、その後再び "フリーズ" します。
これがどのように実装されているかわかりますか?Enumerator は以下の呼び出しをラップしています。
an_iterator
の呼び出しをファイバーで包み、そのブロックを
ファイバーを一時停止する
. そのため、毎回
an_iterator
がブロックに降伏するたびに、それが実行されているファイバーは中断され、メインスレッドで実行が継続されます。次に
next
を呼び出すと、ファイバーに制御が渡されます。
を呼び出すと、ブロックは
そして
an_iterator
はそれが去ったところから続けます。
ファイバーなしでこれを行うために何が必要かを考えるのは有益でしょう。内部と外部の両方のイテレータを提供したい EVERY クラスは、次の呼び出しの間に状態を追跡する明示的なコードを含む必要があります。
next
. next を呼び出すたびにその状態をチェックし、値を返す前にそれを更新しなければなりません。ファイバーを使用すると
自動的に
内部イテレータを外部イテレータに変換することができます。
これはファイバーとは関係ありませんが、もうひとつEnumeratorでできることを紹介します。それは、高階の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]
ブロックなしでイテレータを呼び出すとEnumeratorが返され、それに対して他の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]
これがボンネットの中でファイバーを使用しているかどうかは分かりませんが、使用することは可能です。ファイバーは無限リストや系列の遅延評価を実装するために使うことができます。Enumerator で定義された遅延メソッドの例として、私はここでいくつかのメソッドを定義しています。 https://github.com/alexdowad/showcase/blob/master/ruby-core/collections.rb
ファイバーを使って汎用のコルーチン機能を構築することもできます。私はまだ自分のプログラムでコルーチンを使ったことはありませんが、知っておいて損はない概念だと思います。
これで少しは可能性が見えてきたのではないでしょうか。冒頭で述べたように、ファイバーは低レベルのフロー制御のプリミティブです。これにより、プログラム内で複数の制御フローの位置 (本のページにおける異なるしおりのようなもの) を維持し、必要に応じて切り替えることができます。任意のコードをファイバーで実行できるので、ファイバー上のサードパーティのコードを呼び出して、それが自分の制御するコードにコールバックされたときに、フリーズして別の処理を継続することができます。
多くのクライアントにサービスを提供するサーバー プログラムを書いているとします。クライアントとの完全な対話は、一連のステップを通過することを含みますが、各接続は一時的であり、接続間で各クライアントの状態を記憶する必要があります。(Webプログラミングのように聞こえますか?)
その状態を明示的に保存し、クライアントが接続するたびに (次に何をしなければならないかを確認するために) それをチェックするのではなく、各クライアントのファイバを維持することができます。クライアントを特定したら、そのファイバーを取り出して再スタートさせます。そして、接続が終わるたびにファイバーを一時停止して、再び保存します。この方法では、すべてのステップを含む完全な対話のためのすべてのロジックを実装するために、まっすぐなコードを書くことができます (プログラムがローカルで実行されるように作られている場合は、当然そうなります)。
このようなことが実用的でない理由はたくさんあると思いますが (少なくとも今のところは)、もう一度言いますが、私はただ可能性のいくつかをお見せしたいだけなのです。一度コンセプトを理解すれば、他の誰も思いつかないようなまったく新しいアプリケーションを思いつくかもしれません!
関連
-
[解決済み] Rubyで「例外 => e」を救済するのはなぜ悪いスタイルなのですか?
-
[解決済み] PHPでは、クロージャとは何ですか?なぜ "use "識別子を使用するのですか?
-
[解決済み】なぜFunc<T>ではなくExpression<Func<T>を使うのですか?
-
[解決済み】RubyにあってPythonにないもの、またその逆は何ですか?
-
[解決済み] ファイルのテキストをパターン検索し、指定された値で置き換える方法
-
[解決済み] 文字列を正規表現に変換する ruby
-
[解決済み] このタスクを実行するには、Ruby と Sass をインストールし、PATH に配置する必要があります」という警告を解決するには?
-
[解決済み] 2つのハッシュを比較するにはどうすればよいですか?
-
[解決済み] Sinatraがファイルを変更するたびに自動で再読み込みするようにするには?
-
[解決済み] Ruby初心者が注意すべきRuby Gotchasとは?[クローズド]
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] ファイルのテキストをパターン検索し、指定された値で置き換える方法
-
[解決済み] インストールされているすべてのGemsを削除して最初からやり直す
-
[解決済み] 今月の名前(Date.today.monthをnameに変換)。
-
[解決済み] Rubyのプライベートモジュールメソッド
-
[解決済み] Ruby: 文字列の最初の文字を取得する方法
-
[解決済み] Herokuの基本的なアプリのロードに2秒かかるのはなぜですか?
-
[解決済み] メソッドから2つ以上の値を返す
-
[解決済み] Rubyで配列を一度に初期化するには?
-
[解決済み] メソッド名の最後にある「!」や「?」は何のため?
-
[解決済み] FactoryGirlのbuildメソッドとcreateメソッドの違いは何ですか?