4

最近、「Programming Ruby 1.9&2.0」という本を読み始めました。明示的な列挙子のトリックを示しています

triangular_numbers = Enumerator.new do |yielder|
number = 0
count = 1
    loop do
        number += count
        count += 1
        yielder.yield number
    end
end
5.times { print triangular_numbers.next, " " }
puts

なぜ、この yielder.yield が一時的にループを抜け、次の列挙子オブジェクトが作成されるまでnumberの値を返すのか疑問に思います。ループブロック内でyieldが発生した場合は、通常のケースとは異なるようです。APIdock を確認すると、Proc.yield() のソース コードが Proc.call() と同じであることがわかりました。Enumerator クラスの Yielder オブジェクトの場合、Yielder は yield() をオーバーライドしています。しかし、yielder.yield が一時的にループブロックを離れるのはなぜでしょうか?

参考: APIdock Yielder yield()Ruby MRI rb_proc_call

4

2 に答える 2

9

Ruby の yieldステートメントを Enumerator::Yielder の yieldメソッドと Proc の yieldメソッドと混同しています。スペルは同じかもしれませんが、まったく別物です。

声明

yield ステートメントにはレシーバーがありません。メソッド内では、「今すぐブロックを実行する」ことを意味します。ブロックがアタッチされていない場合、エラーが発生します。ブロックを実行したいだけの場合もあるため、常に引数が与えられるわけではありません。

def foo
  yield :bar
end
foo # LocalJumpError
foo { |x| puts x } # bar

列挙子::Yielder

譲歩者の場合、yieldほとんどの場合引数が与えられます。それ<<は、「次に誰かがnext私を呼んだときは、この価値を与えてください」と同じ意味だからです。

Enumerator.new { |yielder| yielder.yield 3 }.next # 3
Enumerator.new { |yielder| yielder << 3 }.next # same thing

<<yield ステートメントとの混同を避けるために使用することをお勧めします。

プロシージャ

Procs とラムダは基本的に関数です。here は、 「関数を呼び出すだけ」とyield同じことを意味します。callproc の定義方法に応じて、引数を指定することも指定しないこともできます。ここには派手なものはありません。

proc { |x| puts x }.yield(:bar) # bar
proc { |x| puts x }.call(:bar) # same thing as previous line

callyield ステートメントとの混同を避けるために使用することをお勧めします。

于 2013-09-18T13:50:21.337 に答える
1

私も本の中でその例に出くわしました。この例がどのように機能するかを考え、Ruby のドキュメントを閲覧した後、Enumerator が舞台裏で使用していると思われる Fiber クラスを見つけました。

http://www.ruby-doc.org/core-2.0/Fiber.html

ファイバーの概念は、非常に興味深い「軽量の協調的並行性」を実装しています。これは、理解するのがそれほど難しくなく、さらに重要なことは、ブロックを呼び出したり、スレッド制御を処理したりするための他の「利回り」とは異なることです。

Enumerator には、ブロックに渡すファイバー オブジェクトが含まれていると思います。次に、Enumerator で「next」を呼び出すたびに、Fiber オブジェクトで「resume」を呼び出して次の数値を計算できるようにし、ブロックがファイバーで「yield」を呼び出すと、制御は「次の」方法。等々。

Enumerator の可能な実装の私のバージョンは次のとおりです (もちろん、本の例で説明されている部分のみ):

class MyExplicitEnumerator

  def initialize (&block)
    @yielder = Fiber.new { block.call Fiber }
  end

  def next
    @yielder.resume
  end

end

e = MyExplicitEnumerator.new do |yielder| 
    number = 1
    loop do
      yielder.yield number
      number += 1
    end
  end

p e.next
p e.next

# output
# 1
# 2
于 2014-06-13T05:35:59.620 に答える