liveBook · Manning

Enumerator

As explained in The Well-Grounded Rubyist, Fourth Edition

An enumerator is a Ruby object that represents and controls iteration over an enumerable source, optionally allowing iteration without supplying a block immediately.

Call an iterator without a block and you often get an Enumerator instead of an immediate result: `str.each_byte` returns an object ready to supply the string’s bytes, while `str.each_byte { |b| puts b }` performs the iteration. An enumerator is an object, not an iterator method or a container of copied elements. Its `each` obtains values from a defined iteration procedure, and Enumerable supplies methods such as `map`, `select`, and `take` on top of that procedure. section 10.9.3 section 10.9

There are three common ways to establish that procedure:

  • Call `Enumerator.new` with a block receiving a yielder. Each `y << value` specifies a value to yield; traversal runs the block, so it can compute values rather than store a fixed collection. section 10.9.1
  • Call `object.enum_for(:method)` or `object.to_enum(:method)` to attach `each` to a method that yields values. Omit the method name to use `:each`; additional arguments are passed to the attached method. section 10.9.2
  • Let a blockless iterator return one implicitly. For example, `array.map` makes an enumerator whose `each` invokes the array’s `map` operation when given a block. section 10.9.3 section 10.10.1
e = Enumerator.new do |y|
  y << 1
  y << 2
  y << 3
end

This small constructor supplies three values through the yielder; the resulting enumerator can be traversed with `to_a` or transformed with `map`. section 10.9.1

The attached method matters. `array.map` followed by the enumerator’s `each { |x| x.capitalize }` returns a mapped array, not the original array: that `each` is a front end to `map`. An enumerator also has its own Enumerable methods. Thus a hash’s `select` returns a hash, but `hash.to_enum.select { ... }` uses Enumerable’s `select` and returns an array of selected key-value pairs. An enumerator is not a general-purpose proxy for its source. section 10.10.1 section 10.13

Because an enumerator is an object, it can track progress. `e.next` obtains one value; `e.rewind` returns its position to the start. This permits stop-and-start traversal rather than requiring one complete iterator call. An enumerator can even make a non-Enumerable object usable with Enumerable methods: attach `enum_for(:play)` to an object whose `play` method yields notes, then call `map` or `select` on the enumerator. section 10.10.3 section 10.10.4

Enumerators also carry iteration instructions into method chains. `animals.each_slice(2).map { |first, second| ... }` hands pairs to `map` without first building an entire array of slices. By contrast, a bare `animals.map.select` has no mapping block to remember, so it offers little over `animals.select`. Check what an intermediate expression actually returns: slice-family methods return enumerators, and `to_a` collects their results when an array is needed. section 10.11.1 section 10.5.4

An enumerator can provide a limited protective view: returning `@cards.to_enum` lets callers iterate over a deck’s cards but not call the array’s `<<` on the returned object. It does not make the underlying array impossible to alter. Also watch captured state: arguments passed to `enum_for`, including a mutable starting string for `inject`, remain the same objects on later traversals. section 10.10.2 section 10.9.2

How The Well-Grounded Rubyist, Fourth Edition covers it

How deep it goes
Works through construction, method attachment, return values, stateful traversal, chaining, and mutation caveats using runnable examples.
Examples
Demonstrates byte iteration, mapped arrays, a yielding music scale, paired animals, and a protected playing-card deck.
What it assumes
Assumes familiarity with Ruby blocks and iterator calls; teaches how Enumerable methods arise from an enumerator’s each.
What you can do after
Build it from scratch with Enumerator.new, or apply it via enum_for to adapt and control an existing iteration.
How it connects
Connects enumerators to iterator methods, code blocks, Enumerable, each, method chaining, and Yielder.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of The Well-Grounded Rubyist, Fourth Edition. The section links point to the parts of the book it draws on.