It might help if you first understand how yield works. Here is an example:
def do_stuff
if block_given?
yield 5
else
5
end
end
result = do_stuff {|x| x * 3 }
puts result
--output:--
15
In the the do_stuff method call:
do_stuff {|x| x * 3 }
..the block is like a function, and it is passed to the method do_stuff. Inside do_stuff, yield calls the function and passes the specified arguments--in this case 5.
Some important things to note:
yield is called inside a method
When you call a method, you can pass a block to the method
yield is used to call the block.
Okay, now let's look at your comment question:
Is it true that
e = Enumerator.new do |y|
y << 1
y << 2
y << 3
end
is exactly the same as
e = Enumerator.new do #I think you forgot to write .new here
yield 1
yield 2
yield 3
end
In the second example, there is no method definition anywhere--so you can't call yield. Error! Therefore, the two examples are not the same.
However, you could do this:
def do_stuff
e = Enumerator.new do
yield 1
yield 2
yield 3
end
end
my_enum = do_stuff {|x| puts x*3}
my_enum.next
--output:--
3
6
9
1.rb:12:in `next': iteration reached an end (StopIteration)
from 1.rb:12:in `<main>'
But that is a funny enumerator because it doesn't produce any values--it just executes some code(which happens to print some output), then ends. That enumerator is almost equivalent to:
def do_stuff
e = Enumerator.new do
end
end
my_enum = do_stuff
my_enum.next
--output:--
1.rb:7:in `next': iteration reached an end (StopIteration)
from 1.rb:7:in `<main>'
When an enumerator cannot produce a value, it raises a StopIteration exception. So in both cases, the enumerator couldn't produce a value.
But it's still not clear to me what the "yielder" is doing. It looks
like it is collecting all the calculated values so that it can
regurgitate them later when you use the enumerator. If that's the
case, then it seems like it would only be practical for "small"
sequences....you wouldn't want to make an enumerator that stored 50
million items away.
No. In fact, you can create an enumerator that produces an infinite number of values. Here is an example:
e = Enumerator.new do |y|
val = 1
while true
y << val
val += 1
end
end
puts e.next
puts e.next
puts e.next
--output:--
1
2
3
Adding some debugging messages should prove insightful:
e = Enumerator.new do |y|
val = 1
while true
puts "in while loop"
y << val
val += 1
end
end
puts e.next
--output:--
in while loop
1
Note that the message only printed once. So something is going on that is not obvious:
e = Enumerator.new do |y|
val = 1
while true
puts "in while loop"
y << val
puts "just executed y << val"
val += 1
end
end
puts e.next
--output:--
in while loop
1
Because the message "just executed y << val" does not show up in the output, that means execution must have halted on the line y << val
. Therefore, the enumerator did not continuously spin the while loop and insert all the values into y--even though the syntax is exactly the same as pushing values into an array: arr << val
.
What y << val
really means is: when e.next() is called produce this value, then continue execution on the next line. If you add another e.next to the previous example, you will see this additional output:
just executed y << val
in while loop
2
What's happening is that execution always halts when y << val
is encountered in the code. Then calling e.next produces the value on the right side, then execution continues on the next line.
It would probably have made more sense if ruby had made the syntax for the yielder statement like this:
y >> val
And we could interpret that as meaning: halt execution here, then when e.next is called produce val.
David Black recommends not using the y.yield val
syntax, which is equivalent to y << val
lest readers think it works similarly to the yield statement. y.yield val
should be interpreted as: "stop execution here, and when next is called produce val, then continue execution on the next line. Personally, I think that the syntax y << val
stands out more than y.yield val
, so it is easier to spot in the code and readily identify where execution halts.