What is reduce and why should I care?

Functional programming is in many cases tied to immutability and lack of for-loops that are available in the procedural paradigm. Instead we’re told to use concepts like recursion or the more high level toolkit that belongs of map, filter and similar functions.

While map for example is a very good tool which should be used as often as possible it just simply can’t do certain kinds of tasks. As an example map is strictly used for 1:1 transformations where the amount of items in the list we’re performing the map on will be the same in the result. To get around that problem we could potentially do something like this:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|> Enum.map(fn (item) ->
  if item % 3 != 0 do
    item * 2
  else
    nil
  end
 end) # [1, 4, nil, 8, 10....]
|> Enum.reject(fn (item) -> item == nil end)
#=> [2, 4, 8, 10, 14, 16, 20]

This solves the problem 100% but it’s also an operation that forces us to iterate and perform another step which not only causes us to cycle the list twice, which means that for every item in the initial list, we’ll require double the amount of time. This is not an issue if we’re working with smaller datasets since twice the amount of time on almost nothing is still almost nothing.

Enter reduce

The reduce function is what we can call a lower level abstraction of most of the functions we’re using to perform transformations on a list. In fact, you can use reduce to reimplement most other functions available on lists/arrays but you can’t do it the other way around. So how does it work?!

In order to understand reduce it helps to first first think of data as immutable, once it’s defined it might not be changed.

When we’ve accepted the fact that we cannot ever change anything we’ll come to the realization that in order for us to perform transformations, our only chance is by creating new things. This is exactly what reduce is about, we’re using the knowledge of something we have, in order to create something new, one step at a time. Summation is a common example of how we can transform a list of items into something else, let’s use reduce to subtract money with multiple currencies from a bank account instead:

purchases = [
  %Money{amount: 5, currency: :SEK},
  %Money{amount: 9, currency: :USD},
  %Money{amount: 1, currency: :SEK},
  %Money{amount: 3, currency: :USD},
  %Money{amount: 7, currency: :USD},
]
bank_account = %BankAccount{
  SEK: 5000,
  USD: 127,
}

Enum.reduce(list, bank_account, fn (purchase, bank_account) ->
  IO.inspect({bank_account, purchase})

  purchase_currency = purchase.currency
  amount_after_purchase = bank_account[purchase.currency] - purchase_currency

  if amount_after_purchase < 0 do
    raise "Not enough funds on your bank account"
  end

  %BankAccount{bank_account | ^purchase_currency: amount_after_puchase}
end)

If we were to run this code we’d get logs that states the current value of the bank account in each iteration and the purchase that is going to be processed:

# Data for processing first purchase
{%BankAccount{SEK: 5000, USD: 127}, %Money{amount: 5, currency: :SEK}}

# Before processing second purchase
{%BankAccount{SEK: 4995, USD: 127}, %Money{amount: 9, currency: :USD}}
{%BankAccount{SEK: 4995, USD: 118}, %Money{amount: 1, currency: :SEK}}
{%BankAccount{SEK: 4994, USD: 118}, %Money{amount: 3, currency: :USD}}
{%BankAccount{SEK: 4994, USD: 115}, %Money{amount: 7, currency: :USD}}

# Lastly the last operation will be processed and the bank account will be
# returned since that is the value we're transforming into.
# => %BankAccount{SEK: 4994, USD: 108}

So that is how we can use reduce to do a form of summation (we’re using the bank account as the initial value and applying a list of data with the help of a reducer function).

Other use cases

reduce isn’t always about reducing a list of values into one value, it could also be about performing business logic in an efficient matter. As an example it’s quite common that we’d like to perform multiple blocks of logic onto a list of items. Let’s consider the case where we’d like to group a list of people by the first letter of their last name but only show adults in the result:

persons = [
  %Person{first_name: "Eli", last_name: "Wood", age: 31},
  %Person{first_name: "Karina", last_name: "Becker", age: 17},
  %Person{first_name: "Albert", last_name: "Horton", age: 22},
  %Person{first_name: "Margaret", last_name: "Winston", age: 67},
]

persons
Enum.reduce(%{}, fn (person, grouped_persons) ->
  if person.age =< 18 do
    grouped_persons
  else
    last_name_initial = String.at(person.last_name, 0)
    %{grouped_persons | ^last_name_initial: (grouped_persons[last_name_initial]
}} []) ++ [person]}
  end
end)

With reduce we’re able to do both things in one pass over the list instead of two, which we could have done by using filter and group_by.

Closing words

While reduce isn’t necessary in most cases (especially where performance doesn’t matter) it can for the most part be replaced with functions such as map, filter, group_by, sum, min, max. Since those are higher level and their descriptive nature makes them good options for both readability and maintainability.

However there might be cases where the problem requires a more flexible tool or where milliseconds can be the difference being a good and a bad experience. For those cases it could be good to know how you can utilize the powerful reduce to be able to reach your goals!