Getting the Hang of Elixir's Enum.reduce
22 Jan 2015Reduce (also called fold or left fold in other languages) is the backbone of the Enum module. Take a minute to look at the functions that Enum defines. The majority of them are implemented using reduce. So what exactly is it and how can we use it in our code?
Reducing an enumerable is a lot like making cookie dough. Seriously, just stick with me. You have a collection of ingredients: eggs, flower, sugar, etc. You enumerate over that collection of ingredients, one by one, mixing each to a bowl until they form dough. You have conceptually reduced that collection of ingredients into a single thing, the cookie dough.
Enum.reduce has 3 parts: an enumerable (the ingredients), an accumulator (the combined ingredients as we are adding them to the bowl), and a function to apply to every item of the enumerable (the mixing action itself).
Let's break down a simple Enum.reduce example that sums all of the items of a list with our initial state of the sum being 100.
Enum.reduce [1, 2, 3], 100, fn(num, acc) ->
num + acc
end
# returns 106
Enum.reduce's first parameter is an enumerable, in this case it is simply a list of integers. The second parameter is the initial state of our accumulator. We will see how that comes into play in just a minute. The third parameter is the function we want to run for every item in the enumerable. In this example we are adding each item of the list to the accumulator.
The first time the function is invoked it uses our initial accumulator, 100, as the accumulator value. So when we process the first element in the list, the function body looks like this:
1 + 100 # num + acc
Remember the value returned from this function is used as the accumulator for the next item of the enumerable. So when we get to the second element of the list the function body looks like this:
2 + 101
# num + acc (where accumulator is the value returned previously by the function)
And the third:
3 + 103
# num + acc (where accumulator is the value returned previously by the function)
The final value returned by Enum.reduce is the accumulator, in our example: 106.
The real fun in using Enum.reduce starts when you move beyond simply dealing with numbers as the accumulator. The accumulator can be any kind of value. Take a look at the following code block and see if you can guess what commonly used function it implements:
def commonly_used_function(list, fun) do
Enum.reduce(list, [], fn(item, acc) ->
[fun.(item)|acc]
end)
|> Enum.reverse
end
If you guessed map then give yourself a high five.
Enum.reduce is a powerful tool. Spend some time playing around with it, you won't regret it! Here are a few suggestions of things to try building with it:
- Recreate Enum.max
- Given a string count the number of occurrences for each letter and store the result in a map
- Given a list of integers return a list of all numbers that are multiples of 3. You can do this by first implementing Enum.filter on your own...