Enumerable
In Ruby, Enumerable is an incredibly powerful module that provides common querying, searching, filtering, sorting and iteration methods to classes which include it. The only prerequisite is that the class implements an each method that yields elements to a given block.
The most common way to interact with Enumerable is through collections like Array, Hash and Set, which include the module. This gives us access to a recognisable set of methods that make iterable data a joy to work with.
numbers = [1, 2, 3, 4, 5, 6]
even_numbers = numbers.select { |n| n.even? }
# => [2, 4, 6]
squared_numbers = numbers.map { |n| n * n }
# => [1, 4, 9, 16, 25, 36]
sum_of_numbers = numbers.sum
# => 21
names_and_ages = { "Alice" => 30, "Joy" => 9, "Charlie" => 35, "Blake" => 28 }
adults = names_and_ages.select { |name, age| age >= 18 }
# => { "Alice" => 30, "Charlie" => 35, "Blake" => 28 }
uppercased_names = names_and_ages.map { |name, age| name.upcase }
# => ["ALICE", "JOY", "CHARLIE", "BLAKE"]
total_age = names_and_ages.sum { |name, age| age }
# => 102
As of Ruby 4.0, Enumerable provides 48 methods, and I encourage you to explore them in the official documentation. Many of these individual methods will be subject to future reasons why I love Ruby, which is why I've held off showcasing the more interesting ones here.
Using Enumerable in your own classes
While Enumerable is most commonly used with built-in collections, you can also include it in your own classes. All you have to do is implement each and you're good to go.
class FibonacciSequence
include Enumerable
def initialize(limit)
@limit = limit
end
def each
a, b = 0, 1
while a <= @limit
yield a
a, b = b, a + b
end
end
end
fibonacci = FibonacciSequence.new(100)
even_fibonacci_numbers = fibonacci.select { |n| n.even? }
# => [0, 2, 8, 34]
sum_of_fibonacci = fibonacci.sum
# => 231
In my experience, it is rare to actually need to implement a dedicated Enumerable class, and I prefer to simply work with Ruby's built-in collections when possible. But there are certainly use-cases where this comes in handy.
History
Enumerable has been part of Ruby from the very first release in 1995. The idea of providing a common protocol for iterable collections was likely taken from Smalltalk with its Iteration and Collection abstractions.
The Enumerable module keeps evolving with new methods being added over time. Some highlights include:
- Ruby 2.0 - Added Enumerable#lazy and introduced Enumerator::Lazy, enabling composable, non-eager pipelines. We may discuss lazy enumerables in a future reason!
- Ruby 2.2 - Added Enumerable#slice_after and Enumerable#slice_when.
- Ruby 2.4 - Added Enumerable#sum and Enumerable#uniq.
- Ruby 2.5 - Enhanced all?, any?, none?, and one? to accept a pattern argument (class, module, regex), reducing boilerplate blocks.
- Ruby 2.6 - Added Enumerable#chain and improved Enumerable#to_h to accept a block mapping elements to key/value pairs.
- Ruby 2.7 - Added Enumerable#filter_map and Enumerable#tally.
- Ruby 3.1 - Added Enumerable#compact.
Beyond Ruby, plenty of other languages have adopted similar concepts. C# has the IEnumerable interface, Rust has the Iterator trait, Swift has the Sequence protocol, Elixir has the Enumerable module, etc.
It should also be noted that functional programming languages have recognized the power of mapping, filtering and reducing collections for a long time, with languages like Lisp and Haskell having similar capabilities for decades.