The classic iterator pattern describes a way of accessing the elements of an aggregate object without exposing its implementation. This pattern comes in two flavors: external and internal. An external iterator is controlled by the client, while an internal iterator is controlled by the aggregate object.
In Ruby, internal iteration is the norm. Ruby’s Enumerable
module adds several traversal, searching, and sorting algorithms to the main collection classes, Array
and Hash
. While working with Enumerable
you may have come across Enumerator
, Enumerable
‘s close relative.
Enumerator
is an Enumerable
plus external iteration. In this post, we’ll take a look at the basics of Enumerator
s and some of the powerful functionality that they make possible.
One way to create an Enumerator
is by passing a block to its constructor. A yielder object will be passed to this block. The yielder’s #<<
method can be used to define the elements the Enumerator
will iterate through. Enumerator#next
can then be used to provide a controlled, external iteration.
Each time Enumerator#next
is called, the Enumerator
will proceed to the next yielded value. It will
then yield the value, giving control back to the caller. On the subsequent call to Enumerator#next
, the Enumerator
will resume execution up to the next yielded value. That’s how an Enumerator
maintains its current position in the iteration. After the last yielded value, a StopIteration
error will be raised. Interestingly, StopIteration
is rescued by Kernel#loop
.
The more common way to create an Enumerator
is from an Enumerable
object, specifically an object that defines an #each
method. Object#to_enum
(aliased to Object#enum_for
) is implemented to return a new Enumerator
that will enumerate by sending #each
to its receiver.
Enumerator
itself is Enumerable
, so we can use the full power of Enumerable
.
Object#to_enum
takes a method name to generate the Enumerator
. The default is #each
. Passing in an alternative method can result in some strange behavior.
In this example, we created an Enumerator
based on Array#map
. This causes Enumerator#each
to act like #map
. Enumerator#with_index
was then used to create a #map_with_index
method.
Several Enumerable
methods that take a block will return an Enumerator
when called without a block. For example, calling Array#select
without a block will return an Enumerator
with an #each
method that will filter like #select
. These blockless calls offer a concise alternative to Object#to_enum
.
So far, we’ve looked at how to construct Enumerator
s from scratch and from Enumerable
s. However, several non-Enumerable
classes also return Enumerator
s. Since Enumerator
is Enumerable
, this allows you to effectively turn a non-Enumerable
class into an Enumerable
one.
Integer
, for example, returns an Enumerator
from #times
, #upto
, and #downto
.
String
has several iteration methods that will return an Enumerator
when not given a block.
An Enumerator
‘s external iteration can be used to construct infinite lists.
An Enumerator
will yield one value at a time. It pauses after each yield. When asked for another value, it will resume immediately after the last yielded value, and execute up to the next yielded value. In the above example, we used Kernel#loop
to prevent the enumeration from ever stopping.
One problem with infinite lists is that an internal iteration method, for example #select
, will hang forever. This is because methods like #select
and #map
rely on filtering/transforming the entire collection, i.e., they’ll ask for the entire infinite list.
Continuing with our above infinite list example:
Lazy versions of iternal iteration methods provide a solution.
Enumerator#lazy_select
returns a new Enumerator
that will yield each of its values that the given block returns true for. Enumerable#first
is necessary to limit the number of results.
Enumerator
s seem to be everywhere in Ruby’s standard library. External iteration, infinite lists, and lazy iteration, while interesting, are probably not something you’ll ever use. However, an Enumerator
‘s ability to turn non-Enumerable
s into Enumerable
s can result in some pretty elegant code. Ruby 1.9 put Enumerator
s on center stage by integrating them tightly with Enumerable
. Since Enumerable
is integral to just about every Ruby program, it’s important to know what’s possible with its close relative Enumerator
.