Enumerator
Building on yesterday's reason about Enumerable, today I'd like to take a quick look at Enumerator.
Whenever you call an Enumerable method without a block, it returns an Enumerator object instead of executing the method immediately. This allows for lazy evaluation and chaining of operations (though we'll only focus on chaining for today).
languages = %w[Ruby JavaScript TypeScript]
ranked_languages = languages
.map.with_index(1) { |language, rank| "#{rank}. #{language}" }
# => ["1. Ruby", "2. JavaScript", "3. TypeScript"]
ids = 1..23
batches =
ids
.each_slice(5) # yields slices of 5 elements each
.with_index(1) # passing 1 starts index at 1 instead of 0
.map { |ids, batch_number| { batch_number:, ids: } }
# => [{ batch_number: 1, ids: [1, 2, 3, 4, 5] }, ...]
const languages = ['Ruby', 'JavaScript', 'TypeScript'];
const rankedLanguages = languages
.map((language, index) => `${index + 1}. ${language}`);
// => ['1. Ruby', '2. JavaScript', '3. TypeScript']
const ids = Array.from({ length: 23 }, (_, i) => i + 1);
const batches = [];
for (let i = 0; i < ids.length; i += 5) {
batches.push({
batchNumber: Math.floor(i / 5) + 1,
ids: ids.slice(i, i + 5),
});
}
// [{ batchNumber: 1, ids: [1, 2, 3, 4, 5] }, ...]
Note how in the Ruby example we chain methods like map, each_slice and with_index seamlessly, which keeps the code concise and expressive, virtually free of "computer language".
Using Enumerator directly
You can also create your own Enumerator objects using the Enumerator.new method. Here's how to create a Fibonacci sequence generator. Feel free to compare with yesterday's example using Enumerable and think about when you'd choose one approach over the other.
fibonacci = Enumerator.new do |yielder|
a, b = 0, 1
loop do
yielder << a
a, b = b, a + b
end
end
fibonacci.take(10)
# => [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
History
The Enumerator class was introduced in Ruby 1.8 (released in 2003) to provide a way to create external iterators. However, Enumerable methods didn't start returning Enumerator objects when no block was given until 1.8.7 (released in 2008), and the behavior wasn't fully standardized until Ruby 1.9. My personal journey with Ruby began in 1.8.5, so I remember the excitement as these improvements rolled out!