An enumerator is like a Pez® dispenser, which ejects a peppermint candy each time the top of the dispenser is pressed. An enumerator version of the dispenser would not hold a supply of candies, but would manufacture the candies one at a time, on demand, possibly being capable of producing an infinite number of them.
One type of an enumerator is tied to an underlying collection of objects. Here are two that are tied to an array.
enum = [1,2,3].each #=> #<Enumerator: [1, 2, 3]:each>
enum.next #=> 1
enum.next #=> 2
enum.next #=> 3
enum.next #=> StopIteration (iteration reached an end)
enum = [1,2,3].cycle #=> #<Enumerator: [1, 2, 3]:cycle>
enum.next #=> 1
enum.next #=> 2
enum.next #=> 3
enum.next #=> 1
enum.next #=> 2
... ad infinitum
enum.first(8)
#=> [1, 2, 3, 1, 2, 3, 1, 2]
In the first example only a finite number of objects are generated by the enumerator before a StopIteration
exception is raised. In the second example an arbitrary number of objects can be generated, but only on demand. first
, for example, instructs enum
8
times to generate and pass to itself one object. enum
is not lazy; it is all-to-eager to comply, but will not manufacture and dispense an object until it is instructed to do so.
The other type of enumerator generates objects according to a set of rules that it was born with, rules that are not tied to an underlying object. Those enumerators are generally capable of generating an infinite number of objects. The enumerator that generates Fibonacci numbers is an example of that type of enumerator. It is not a loop that does not terminate; it is a machine that is capable of producing any number of objects, but only one at a time, on demand.