liveBook · Manning

Enumerable protocol

As explained in The Well-Grounded Rubyist, Fourth Edition

An enumerable object is a Ruby object whose elements or generated values can be traversed through an each method and processed with Enumerable methods.

Suppose an object can yield values one at a time. In Ruby, the usual way to give it collection-style operations is to include Enumerable and define an instance method named each. That method yields values to a block; Enumerable uses those yields to implement methods such as find, map, select, and first. The object need not store its values in an array. section 10.0 section 10.1

Listing 10.1 An Enumerable class and its deployment of the each method
class Rainbow
  include Enumerable
  def each
    yield "red"
    yield "orange"
    yield "yellow"
    yield "green"
    yield "blue"
    yield "indigo"
    yield "violet"
  end
end

A small class yields colors directly from each. Including Enumerable makes methods such as find available without defining them in the class. section 10.1

The meaning of an element comes from each, not from Enumerable. An array yields its members in sequence; a hash yields key/value pairs as two-element arrays; a file handle yields lines. A range can participate when it can iterate, but a range starting at a float cannot support iteration-based queries. This is why the same Enumerable method can receive different kinds of block arguments on different objects. section 10.1 section 10.2

Once each is in place, the shared operations serve several jobs:

A search built on each

With a color-yielding object that includes Enumerable, `r.find { |color| color.start_with?('y') }` returns `"yellow"`. find calls each, tests each yielded color with the block, and stops at the first match; the class does not need its own find method. section 10.1

Shared methods do not erase class-specific behavior. Hash#select returns a hash, whereas Enumerable’s select returns an array. An enumerator attached to a hash’s each uses Enumerable’s select and returns an array of key/value pairs. Nor must an object itself include Enumerable to expose its yielded values: an Enumerator can attach its own each to another object’s yielding method and provide Enumerable operations there. section 10.10.1 section 10.10.4

Iteration can also produce values indefinitely. Enumerable has first but no general last: an endlessly yielding each has no final value. Operations such as map and select that wait for iteration to finish cannot return a completed result from that endless stream. Choose an operation that stops when its goal is met, or provide a stopping condition. section 10.4.1 section 10.10.4

How The Well-Grounded Rubyist, Fourth Edition covers it

How deep it goes
Works through a custom each implementation, method behavior, hash overrides, and limits of infinite iteration.
Examples
Uses a color-yielding class, hash key/value pairs, Boolean queries over state names, and an endlessly rolling die.
What it assumes
Builds on Ruby classes, modules, methods, and blocks; demonstrates the required each method with a small class.
What you can do after
Build it from scratch by defining each and including Enumerable, then apply shared queries and transformations while respecting iteration limits.
How it connects
Connects Ruby modules and blocks to collections, Enumerator, case equality, and sorting.

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.