How to Use Enum.map & Enum.reduce in Elixir
Map and reduce are two fundamental building blocks of functional programming in languages like Elixir. Both allow you to transform sequences of values and are often combined in a pipeline to perform more sophisticated computation.
The map operation transforms a sequence by applying a function to every value and returning a new sequence. The reduce operation transforms a sequence by combining the values in some way. For a very quick example, imagine we had a list of numbers that we want to multiply by 5, and then up to get the sum. We could map our numbers to perform the multiplication, and then reduce the resulting list to produce the sum.
In this post, we'll explore how to use the common Enum.map
and Enum.reduce
functions in Elixir, along with a few other common variants of these functions.
by Dave Thomas
⭐️⭐️⭐️⭐️
Mapping a simple list
Using Enum.map
, we can transform each element of an enumerable with a function:
iex> Enum.map([1, 5, 10], fn i -> i * 5 end) [5, 25, 50]
The result is always a list of the same size as the original enumerable.
Reducing a simple list
With Enum.reduce
, we can transform the elements of an enumerable in a more open-ended fashion. It's generally used to combine elements into a single value. Like map, a function is invoked for each member of the enumerable, but in addition to the member value, we also receive an accumulator (sometimes also called a memo) value. The function uses the accumulator and the member value to return a new accumulated value.
As an example, let's use reduce to sum up a list of numbers. The second argument below is the initial value of the accumulator. The transformation function simply adds the new value to the accumulator:
iex(12)> Enum.reduce([1, 5, 10], 0, fn i, acc -> acc + i end)
16
Map or Reduce with the Map struct
The map and reduce functions don't just work with lists–they work with other enumerables, like maps and keyword lists. When used with these enumerables, our transformation functions are provided with a tuple of key-value pairs:
iex(3)> Enum.map([x: 1, y: 2, z: 3], fn {k, v} -> v * 3 end)
[3, 6, 9]
iex(4)> Enum.reduce([x: 1, y: 2, z: 3], 0, fn {k, v}, m -> m + v end)
6
Note that the example above will give an unused variable warning for k
, since we don't use it in our function. In a real world usage, we would replace it with _
, but include it in this example to show the exact arguments the function receives.
Using the Capture Operator
In this post we mostly use the Elixir's normal anonymous function definition syntax (fn i -> ... end
), but for simple functions, it's not uncommon to use the capture operator (&
) instead. The following two examples do exactly the same thing:
# Multiple all values by 5
iex> Enum.map(list, fn i -> i * 5 end)
# Multiple all values by 5 using the capture operator
iex> Enum.map(list, &(&1 * 5))
Useful Map Variants
The map function has a couple of variants that can be useful for solving specific problems.
Enum.map_join
can be used to perform an Enum.join
on the results of a map:
iex> Enum.map_join([1, 2, 3], fn i -> i * 2 end)
"246"
Enum.map_intersperse
is used to intersperse values in between mapped values:
iex> Enum.map_intersperse([1, 2, 3], :a, fn i -> i * 2 end)
[2, :a, 4, :a, 6]
And Enum.map_every
can be used to map every Nth item of the enumerable:
iex> Enum.map_every(1..10, 3, fn x -> x + 1000 end)
[1001, 2, 3, 1004, 5, 6, 1007, 8, 9, 1010]
Pipelining
Pipelining in Elixir is a common operation which lets us take the output of one function and pipe it into another. The Elixir pipeline operator, |>
takes the proceeding value and passes it as the first argument to the subsequent function. It's "syntactic sugar" to make chaining functions together easier. The following expressions are equivalent:
# expression without pipelining
function_c(function_b(function_a(value, param_a), param_b))
# the same expression using the pipeline operator
value
|> function_a(param_a)
|> function_b(param_b)
|> function_c
Pipelining with map & reduce is common to help build more sophisticated multi-step transformation.
Going back to the example in the intro: imagine we had a list of numbers that we want to multiply by 5, and then up to get the sum. Using the pieces we just learned about, we can pipeline map and reduce to solve the problem:
numbers = [1, 9, 4]
numbers
|> Enum.map(fn i -> i * 5 end)
|> Enum.reduce(0, fn m, i -> m + i end)
Wrapup
In this post we learned the basic usage of the map and reduce functionality of Elixir's Enum module. But there's a lot more to explore! For further reading, we recommend checking out:
- How to use Enum.find and similar functions in Elixir
- "Lazy" evaluation with the Stream module
- More power pipelines with Kernel.tap and Kernel.then