Lazy pipelines handle huge files and infinite sequences without building giant intermediate arrays.
An Enumerator is an object that represents an iteration. You get one by calling an iterator without a block ([1, 2].each, 5.times, File.foreach(path)), or build one from scratch:
fib = Enumerator.new do |y|
a, b = 0, 1
loop { y << a; a, b = b, a + b }
endEnumerators include Enumerable, so fib.take(10) works even though the sequence is infinite: the generator block only runs as far as needed. They also support external iteration: e.next returns the next value and raises StopIteration at the end, which loop rescues for you. peek looks ahead and rewind restarts.
Normally each Enumerable step builds a full intermediate array: map runs over everything, then select runs over the result. Calling `lazy` turns the chain into an Enumerator::Lazy, which pulls one element at a time through all the steps:
(1..Float::INFINITY).lazy.map { it * 2 }.select { it % 3 == 0 }.first(3)Nothing runs until a terminal method such as first(n), to_a, each or force asks for values. Use lazy chains for infinite sources, huge files (File.foreach(path).lazy) and when you only need the first few results. For small in-memory arrays, the eager version is usually faster because laziness adds per-element overhead.
Enumerator.produce(initial) { |prev| next_value } (Ruby 2.7) is a concise way to generate sequences.
Example
fib = Enumerator.new do |y|
a, b = 0, 1
loop { y << a; a, b = b, a + b }
end
p fib.take(8) # [0, 1, 1, 2, 3, 5, 8, 13]
p fib.lazy.select(&:even?).first(4) # [0, 2, 8, 34]
e = %w[a b].each
p e.next, e.next # "a", "b"
begin
e.next
rescue StopIteration
puts "done" # done
end
calls = 0
result = (1..Float::INFINITY).lazy
.map { calls += 1; it * it }
.reject(&:odd?)
.first(3)
p result, calls # [4, 16, 36], 6
p Enumerator.produce(1) { it * 3 }.take(5) # [1, 3, 9, 27, 81]Remember
- Iterators called without a block return Enumerators
- Enumerator.new and Enumerator.produce build custom, even infinite, sequences
- lazy pulls elements through the chain one at a time until a terminal call
Watch out for
- Calling a non-terminal like map on an infinite range without lazy, which never returns.