Mixing in the Enumerable module provides a class traversal, searching and sorting functionality. In return the module expects a class to have the
<=> methods. To deepen my personal understanding of Enumerable I decided to implement its established
reduce methods. In the context of the Array class I will show you the expectations and implementation I came up with. At the end of this post I will also provide a link to an equivalent endeavor in the Hash class context.
Methods, blocks and yield
In order to implement
each for a class, but also when implementing its functional cousins, we need to understand how methods process blocks and pass arguments to them. In the end we want to be able to provide our own blocks to these methods.
arg is a local variable in the
give_me_a_block method. It is passed into the block using the
x is a block parameter that gets assigned the value of
x is then used as a local variable in the block. The
yield method returns the value of applying the block to its arguments. This value is assigned to
val and can be reused.
A test object
Let us define an Array so we can use it in our expectations.
A requirement for the relevant Enumerable methods to work is the presence of an
each method in a class. Array comes with this method, but why not reinforce our understanding of blocks and the
yield method? When passed a block the
each method typically returns the object it was called on. Therefore we use the accumulator
acc to check if it correctly iterates over all elements from the test object.
Mix Enumerable into Array
Before we define
reduce in our own Enumerable module we mix the empty module into the Array class. This way the methods, that are yet to be defined, will be available as instance methods on Array when we start defining them in the module.
map is defined in terms of
each and sets up an accumulator
acc. All processed elements returning from the block are pushed onto
acc and then the accumulator is returned. This idea of defining a method in terms of
each and using an accumulator will also be present in the implementations of
select method only those elements for which the block returns a truthy value are pushed onto the accumulator.
select acts as a filter because of this conditional accumulation.
This particular implementation of the
reduce method requires a starting value for the accumulator
acc, passed in as an argument. Also it requires you to provide a block with two block parameters instead of one. The first, named
sum in the expectation, is used to iteratively build up a value. The second block parameter
n represents the current element. This allows the method to sum all elements in the test object. In the implementation
acc is assigned the starting value and then passed to the
yield method, together with the current element
elem. From there on it is reassigned the return value of the provided block on each iteration and finally returned.
Voila, all our methods are now in line with the given expectations. Please be aware that this is not a full implementation of the original Enumerable methods. For example, calling
map without a block should return an Enumerator, you should be able to call
reduce without an argument or provide a Symbol as its sole argument, and so on. Nonetheless, I hope the implementation of these methods has given you a better understanding of their semantics and inner workings. If you are interested in the Hash implementation or if you want to check the code visit Understanding Ruby.