Skip to content
AgileLogics
6 min read

Enumerators and Lazy Evaluation

Prerequisites: Comparable and Enumerable

Call most iteration methods without a block, and Ruby returns an Enumerator — an object that remembers how to iterate, ready to be chained or stepped through later.

enum = %w[a b c].each_with_index
p enum.class
p enum.map { |letter, i| "#{i}:#{letter}" }

p %w[x y z].map.with_index(1) { |v, i| "#{i}. #{v}" }
p [4, 5, 6].each_slice(2).to_a
p [1, 2, 3, 4].each_cons(2).map { |a, b| b - a }
Enumerator
["0:a", "1:b", "2:c"]
["1. x", "2. y", "3. z"]
[[4, 5], [6]]
[1, 1, 1]

map.with_index(1) is a common idiom: a map that also gives each element's position, starting at 1.

Custom enumerators

Enumerator.new builds a sequence from a block. yielder << value produces the next value — and the block only runs as far as someone asks:

fibonacci = Enumerator.new do |y|
  a, b = 0, 1
  loop do
    y << a
    a, b = b, a + b
  end
end

p fibonacci.take(10)
p fibonacci.next
p fibonacci.next
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
0
1

That loop is infinite, but the program doesn't hang: the enumerator only computes values when they're requested.

lazy — chains that don't build intermediate arrays

Normally map and select each build a complete new array. On a huge (or infinite) collection, that's slow or impossible. lazy processes elements one at a time through the whole chain:

result = (1..Float::INFINITY).lazy
                             .map { |n| n * n }
                             .select(&:even?)
                             .first(4)
p result
[4, 16, 36, 64]

The same idea keeps memory flat when processing a large file:

errors = File.foreach("huge.log").lazy
             .select { |line| line.include?("ERROR") }
             .map(&:strip)
             .first(10)

File.foreach without a block also returns an enumerator, so only as many lines are read as needed to find ten errors.

Common mistakes

  • Using lazy on small arrays. It adds overhead per element; it's for large or infinite sources.
  • Forgetting to end a lazy chain with first, to_a, or force — without one, nothing is computed.