I struggle a little bit with understanding how the Enumerator.new
method works.
Assuming example from documentation:
fib = Enumerator.new do |y|
a = b = 1
loop do
y << a
a, b = b, a + b
end
end
p fib.take(10) # => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
Where's the loop break condition, how does it know how many times loop should to iterate (as it doesn't have any explicit break condition and looks like infinite loop) ?
Enumerator
uses Fibers internally. Your example is equivalent to:
require 'fiber'
fiber = Fiber.new do
a = b = 1
loop do
Fiber.yield a
a, b = b, a + b
end
end
10.times.map { fiber.resume }
#=> [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
Edit I think I understand your question now, I will still keep my original answer below.
y << a
is an alias for y.yield(a)
, which is basically a sleep
with return value. Each time a value is requested from the enumerator with next
, the execution is continued until another value is yielded.
Enumerators do not need to enumerate a finite number of elements, so they are infinite. For example, fib.to_a
will never terminate, because it tries to build an array with an infinite number of elements.
As such, enumerators are great as a representation of infinite series such as the natural numbers, or in your case, the fibonacci numbers. The user of the enumerator can decide how many values it needs, so in your example take(10)
determines the break condition if you will.
The break condition itself is in the implementation of Enumerator#take
. For demonstration purposes, we can make our own implementation called my_take
:
class Enumerator
def my_take(n)
result = []
n.times do
result << self.next
end
result
end
end
Where you could of course "mentally substitute" your n.times
loop with your classical C style for (i=0; i<n; i++)
. There's your break condition. self.next
is the method to get the next value of the enumerator, which you can also use outside of the class:
fib.next
#=> 1
fib.next
#=> 1
fib.next
#=> 2
fib.next
#=> 3
That said, you can of course build an enumerator that enumerates a finite number of values, such as the natural numbers in a given range, but that's not the case here. Then, the Enumerator will raise a StopIteration
error when you try to call next
, but all values already have been enumerated. In that case, you have two break conditions, so to speak; the one that breaks earlier will then win. take
actually handles that by rescuing from the error, so the following code is a bit closer to the real implementation (however, take
is in fact implemented in C).
class Enumerator
def my_take(n)
result = []
n.times do
result << self.next
end
result
rescue StopIteration
# enumerator stopped early
result
end
end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With