Question

I recently start to read the book "Programming Ruby 1.9&2.0". It shows a trick for explicit enumerator

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

I wonder why this yielder.yield will leave the loop temporarily and also return the value of number until the next enumerator object created. It seems different than the usual case when a yield inside the loop block. I check the APIdock and find that the source code of Proc.yield() is the same as Proc.call(). For the Yielder object in the Enumerator class, the Yielder has override the yield(). But why the yielder.yield will temporarily leave the loop block?

Reference: APIdock Yielder yield(), Ruby MRI rb_proc_call

Was it helpful?

Solution

You are confusing Ruby's yield statement with Enumerator::Yielder's yield method and Proc's yield method. They may be spelled the same but they are completely different.

Statement

The yield statement has no receiver. Inside a method it means "Run the block right now". An error occurs if no block is attached. It is not always given an argument, because sometimes you just want to run the block.

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

Enumerator::Yielder

For a yielder, yield is almost always given an argument. That's because it means the same as << which is "The next time someone calls next on me, give them this value".

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

I think it's a good idea to use << to avoid confusion with the yield statement.

Proc

Procs and lambdas are basically functions. yield here means the same thing as call, which "Just call the function". You can give it an argument or not, depending on how the proc was defined. Nothing fancy here.

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

I think it's a good idea to use call to avoid confusion with the yield statement.

OTHER TIPS

I also stumbled on that example in the book. After some thinking on how the example might work and browsing Ruby doc I found the Fiber class which I think is what is used by Enumerator behind the scene:

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

Fiber concept implements "light weight cooperative concurrency" which is quite interesting, not that difficult to understand and what's more important it's different from other "yields" for calling blocks or dealing with thread controls.

I think Enumerator has a Fiber object inside which it passes to the block. Then looks like every time when you call "next" on your Enumerator, it calls "resume" on the Fiber object to allow it calculate the next number and when the block calls "yield" on the Fiber, the control goes back to the "next" method. And so on.

Here's my version of possible implementation of Enumerator (of course, only that part that is discussed in the book's example):

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
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top