Writing Your Own Enumerables in Ruby

Jason Mattingly
6 min readDec 23, 2019

Here’s an Enumerable:

[1,2,3].each do |item|
puts item
end

We start with an array of items that we want to visit one by one, in this case the numbers 1, 2, and 3.

Calling .each on our array means that we want to visit all 3 of these numbers. The code block that follows .each begins with do and ends with end. We want this code block to execute not once for the array as a single object, but instead multiple times, once for every item inside of the array.

The example code that we want to execute each time is puts item. item is the keyword that we chose to represent whatever thing in the array we’re currently looking at. We defined it immediately after do by placing it between pipes like so: |item|

So what’s going on here? .each is tagged onto the end of an array, which makes it look like each may be a method being called on that array.

Then it seems like we may have two separate code blocks executing: One code block is saying to visit everything in the array, the other code block is defining what to do with each of those items as we’re visiting them.

Let’s rebuild this into our own method. Instead of .each , we’ll write a custom method called .my_each

[1,2,3].my_each=> undefined method `my_each' for [1, 2, 3]:Array

That makes sense because we haven’t yet defined our method anywhere. Since [1,2,3] is an instance of the Array class, let’s make our method an instance method on that class.

class Array
def my_each
end
end
[1,2,3].my_each=> nil

Before we go forward, let’s get our bearings. When we call my_each on an array, what is the thing that we’re working with from within the method? In other words, what is self?

class Array
def my_each
puts "#{self.class}: #{self}"
end
end
[1,2,3].my_each=> Array: [1, 2, 3]

It looks like within our method we’re looking at the entire array that we called my_each on. Since our ultimate goal is not to act on the whole array, but instead to act on the individual components of the array, let’s begin by trying to interact with those individual components. We can do this by iterating through every item in the array and calling puts for each of those items.

class Array
def my_each
for item in self
puts "#{item}"
end
end
end
[1,2,3].my_each=> 1
2
3

This behaves just like the each method we started with except for one big difference: Our custom method my_each is hardcoded to always execute the same code, puts "#{item}" every time the method is called while each allows the caller to specify what code block they want to run on each iteration of the loop.

[1,2,3].each do |item|
puts item + 10
end
=> 11
12
13
[1,2,3].my_each do |item|
puts item + 10
end
=> 1
2
3

While each evaluated the passed in code block and used it to affect its output, our method ignored the given code block entirely and acted like it wasn’t even there. This is because even though Ruby methods can receive and act on code blocks passed to them, those methods receiving the blocks must acknowledge that it was passed and allow it to execute.

One way we can do this is with the yield keyword. Using this keyword says “At the same place I use the yield keyword, I’d like to allow the passed in code block to execute.” Let’s try it.

class Array
def my_each
yield
end
end
[1,2,3].my_each do
puts "Hello"
end
=> "Hello"

By giving the code block permission to execute by using the keyword yield we were able to take a code block passed to our method and execute it just like each does.

Now let’s get the passed in code block to reference our array, since that’s what we’re ultimately going to want to affect.

class Array
def my_each
yield
end
end
[1,2,3].my_each do
puts "#{self}"
end
=> main

Interesting. We already confirmed that [1,2,3] is an instance of Array and that our method my_each is an instance method on Array, so why would self be main instead of [1,2,3]?

The reason is because the passed in code block is evaluated before the method is called. At the time that the code block executes to find out what it is, self is main, so when the method reaches our yield it puts to the screen what self was when it first ran, which is main.

Alright, so it looks like if we want to reference our array we’ll need to do that from within the method. Somehow we need to let the passed in code block know about the array that we want to operate on. Luckily, yield can take parameters and these parameters will allow us to talk to the code block.

class Array
def my_each
yield(self)
end
end
[1,2,3].my_each do |what_am_i|
puts "#{what_am_i}"
end
=> [1,2,3]

We did two important things here. The first thing we did is pass yield a parameter of self . Because my_each is an instance method, we know that from within the method, self is the instance of the Array, in our case [1,2,3].

The second thing we did is give an iterator to the code block that we’re passing in. By adding |what_am_i| our code block is saying “I’m expecting to have access to a parameter that was passed in. I don’t know what that parameter will be, but I want the keyword what_am_i to stand in for it so that I can act on it. When yield passed self and it turned out that self was the array, the code block dutifully took that array and printed it to the screen.

We’re just missing one last piece, which is that we want our code block to execute for every item in the array, not for the array itself! To do that we need to call yield for every item individually.

class Array
def my_each
for item in self
yield(item)
end
end
end

Let’s retry our example from earlier where we were comparing my_each with each

[1,2,3].each do |item|
puts item + 10
end
=> 11
12
13
[1,2,3].my_each do |item|
puts item + 10
end
=> 11
12
13

my_each has quickly become a very powerful method that can also serve as a foundation to make more complicated Enumerable methods, like map.

Ruby’s map method, like each, also takes a code block and performs that code block’s operation on each item in an array. This method however returns a different array, one that’s been transformed by the code block passed to it.

[1,2,3].map do |item|
item * 2
end
=> [2,4,6]

The array itself has been changed! We can do this ourselves just by adding a few extra lines of code.

class Array
def my_each
for item in self
yield(item)
end
end
def my_map
new_array = []
for item in self
new_array << yield(item)
end
new_array
end

end
[1,2,3].my_map do |item|
item * 2
end
=> [2,4,6]

Next let’s try to implement a neat shortcut that map uses when it has a single operation that it wants to perform on every item in the array, which is the syntax that looks like this:

[1,2,3].map(&:even?)=> [false,true,false]

Pretty cool, eh? This syntax says “I want you take the method even?, call it on every item in the array, and then save that return value to the array in the place of the item that was there.” Calling even? on an Integer returns true or false, so that true or false return value is what replaces the number in the array. It’s equivalent to:

[1,2,3].map do |item|
item.even?
end

This syntax makes the call much shorter and saves us the trouble of having to define our iterator, which is pretty handy. Let’s try it with my_map

[1,2,3].my_map(&:even?)=> [false,true,false]

Wow! Works right out of the box without us making any changes at all! Here’s why:

When we pass a symbol as a block parameter instead of passing a block, Ruby automatically calls to_proc on it, since a proc is something that can both hold and later execute block code. The symbol (in our example case :even?) is turned into a proc which can then be called (executed) with an additional parameter. This additional parameter will identify what object we want the code block to execute on. That means that this:

for item in [1,2,3]
yield(item)
end

Becomes this:

for item in [1,2,3]
:even?.to_proc.call(item)
end

Which is equivalent to

for item in [1,2,3]
item.even?
end

Neat.

For more practice with custom Enumerables, try implementing some of the other fun Enumerable instance methods like any?, all?, or select

[1,2,3].any? do |item|
item == 1
end
=> true
[1,2,3].all? do |item|
item == 1
end
==> false
[1,2,3].select do |item|
item == 1
end
==> [1]

Can you improve on these or make your own?

--

--

Jason Mattingly

Solves puzzles and builds software in beautiful downtown Seattle.