A short and sour guide to Reducers

Daniel Merrill
Async
Published in
11 min readMar 12, 2018

If you’ve dabbled in functional programming or a state management tool like Redux, you’ve probably encountered the concept of the reducer function. In fact, if you’re like me, you were clobbered by references to reducers to the point that the word began to lose all meaning.

Let’s zoom out a bit and take a look at how reducers work. They truly are simple beasts, but their simplicity often gets lost in functional programming jargon and the complicated tasks they can solve. Once we get the basics under our belt, we’ll explore their more complex uses.

The Basics

A reducer function provides instructions for combining things. That’s it. Imagine you’re picking lemons off of a tree. You have a bucket, and each time you pick a lemon, you have to decide what to do with it. A sensible instruction would be to put the lemon in the bucket. In JavaScript, that might look like this:

The return statement is how we say “here’s the result of combining these items”, so we’ll return the lemon-in-bucket. Notice that the returned lemon-in-bucket would also make sense as the input bucket — i.e. we could keep putting lemons in our bucket, and it would start to fill up:

Maybe we don’t want to keep all of the lemons though, just the ripe ones. Lets modify our concept of a ‘lemon’ to be an object that has a color:

{ color: 'yellow' } or { color: 'green' }

We’ll put the lemon in the bucket only if it is yellow.

Our putInBucket function already knows how to combine a lemon and a bucket into a lemon-in-bucket, so we can reuse it to put lemons that pass our color test into our bucket. Otherwise, we’ll give the bucket right back without adding anything to it.

Now, when I pick my lemons, I only end up with the yellow ones:

You might say we are ‘accumulating’ lemons in our bucket. In fact, the first argument of a reducer function (our bucket) is often referred to as the accumulator. The second argument is usually referred to as the ‘current value’. (You’ll often see these abbreviated as acc and cur )

But wait! What if we‘re not interested in collecting lemons in a bucket? What if instead we want to tally up how many yellow lemons and how many green ones we come across? Let’s create some instructions for that.

Our tallyLemonColor reducer will have instructions for combining a tally sheet with a lemon. When we come across a green lemon, we’ll increment tallySheet.green, and we’ll increment tallySheet.yellow for yellow lemons:

Just like our bucket began to fill with lemons as we kept picking, our tally sheet begins to fill with tally markings for each color. In this case, the tallySheet is the accumulator.

In any reducer, You can think of the accumulator as a running total. It could be an array full of lemons, an object with tallies, or even a simple number representing the total number of lemons passed through. It’s up to you to decide what makes sense.

Reducing over arrays

You may have noticed a lot of repetition in the examples above. We had to call tallyLemonColor for each lemon we looked at. If I need to tally up an entire lemon tree, I don’t want to have to write out a new line of code for each lemon—I want to write the tally instructions once, and then apply those instructions to the whole tree.

Every JavaScript array has a built-in reduce method that does just this. Here’s the signature: Array.reduce(reducerFn, initialValue)

A reducerFn is also expected to have a consistent signature and return value:

// A reducer takes an accumulator and a current value
// as input and returns an updated accumulator
function reducer(accumulator, currentValue) {
const newAccumulator = resultOfSomeWork()
return newAccumulator
}

When we call reduce on our array, we provide our instructions as the reducerFn (in our case, the putInBucket or tallyLemonColor function), and an initial value (our empty bucket or blank tally sheet). The rest is taken care of for us—each item in the array goes through the reducer function, and each return value becomes the accumulator in the next iteration.

The first pass through, the accumulator is set to the initialValue. After all of the items have been iterated over, the final accumulator is returned as the result of the reduce. Remember the empty bucket filling with lemons? Same idea here.

Let’s create a lemonTree array and tally up some lemons:

We didn’t have to change our tallyLemonColor function at all since it already has the correct reducer signature. We provide an empty tally sheet and store the results in a variable called finalTallySheet. We end up with a completed tally sheet containing the number of green and yellow lemons.

You now know the core functionality of reducers!

In summary:

  • reducer: A function that provides instructions for combining things.
  • accumulator: The running total so far (could be lemons-in-buckets or tallies-on-sheets).The accumulator is the first argument to the reducer.
  • currentValue: The current value passing through the reducer, provided as the second argument to the reducer function.
  • initialValue: The accumulator value before any reducing happens (the empty bucket or the blank tally sheet).

If you’re here to learn the basics, you’re done! If you’re a brave soul and want to dive head first into the deep end of functional programming, read on!

Advanced usage

Sometimes our reductions require multiple steps. Say we have a lemon tree, but want to end up with a box of ripe yellow lemons, labeled by weight, with any underweight lemons removed.

This might require three trips through the array: we first want to reduce our lemon tree down to a bucket of ripe lemons, then we’ll go through that bucket and put a label on each lemon with its weight, and finally we’ll go through the bucket again and throw out all lemons that weigh less than three ounces.

Three passes through our little lemon tree array is not a big deal, but as data becomes larger and reducer functions more computationally intensive, we may start to see performance issues. Ideally, we’d only have to loop through once. We can do this with a transducer.

What’s a transducer?

A transducer is a function that takes a reducer as input and returns another reducer as its output. The reducer it returns may transform the current value or include some filtering logic. We’ll see how we can use this reducer-to-reducer symmetry to our advantage.

Remember our keepRipeLemon reducer from the beginning? There’s something special about it—it already makes use of a reducer-within-a-reducer! Notice that it passes off the instructions for “what to do with ripe lemons” to putInBucket:

At the time, we used putInBucket just because it happened to provide the instructions we needed, but as we’ll soon see, we can use this reducer-within-reducer concept to piece together mega-reducers (not official name).

First, let’s make sure we understand the long-form version where we call reduce one time for each step in the process. We have four total reducers:

  • putInBucket: Instructions for combining a lemon with a bucket.
  • keepRipeLemon: Instructions for testing whether a lemon is ripe and keeping it if so.
  • labelLemonWeight: Instructions for weighing each lemon (a random number between 0 and 12). Adds a weight property to our lemon object before passing it off to putInBucket.
  • keepHeavyLemon: Instructions for filtering out too-light lemons.

Notice that all of our reducers use putInBucket as the instructions for “what to do next”. Find a yellow lemon? Put it in the bucket. Done weighing and labeling? Put it in the bucket. (Find a green lemon? Give the bucket back without doing anything).

The only thing special about putInBucket is that it provides the specific instructions for combining the lemon and bucket— bucket.push(lemon). It doesn’t rely on any other outside instructions to get the job done. Let’s call this kind of self-sufficient function a ‘base reducer’.

But remember, there’s more than one way to combine things — we may want to tally our lemons instead of bucketing them. We’ll make our reducers flexible by passing the “what to do next” instructions as an argument instead of hard-coding putInBucket. Since we’re not sure if we’ll be tallying or bucketing lemons, we’ll also rename our bucket accumulation to runningTotal.

We end up with a function that takes nextInstructions as an argument and returns a reducer:

I find ES6 arrow notation easier to read, so from here on out I’ll use arrows instead of the `function` notation above.

In the example above, keepRipeLemon initially expects instructions for what to do next if the lemon passes the ripeness test. When we call this function with those instructions, we are returned a function with the familiar reducer signature: (acc, cur) => newAcc

We can now load up keepRipeLemon with our putInBucket base reducer if we want our next instructions to be to put ripe lemons in our bucket:

const keepRipeLemonsInBucket = keepRipeLemon(putInBucket)

Or, if we’d rather tally up how many ripe lemons we have, we could load it up with our tally reducer instead:

const tallyRipeLemons = keepRipeLemon(tallyLemonColor)

When we reduce over our lemon tree, we get the same result as when we used our hard-coded reducers:

lemonTree.reduce(keepRipeLemonsInBucket, []) // [{ color: 'yellow'}, { color: 'yellow'}, { color: 'yellow' }]
lemonTree.reduce(tallyRipeLemons, { green: 0, yellow: 0 }) // { green: 0, yellow: 3 }

Our initialValue for keepRipeLemonsInBucket is an empty array, and the initialValue for tallyRipeLemons is an empty tally sheet. We have to respect what type of accumulation our base reducer expects— putInBucket expects a bucket array and tallyLemonColor expects a tally sheet object.

We could say that our reducers are becoming composable. Meaning, we can build up the functionality we want with reusable pieces instead of hard coding one-off functionality. Let’s take this idea one step further.

Making a mega-reducer

You may not have realized it, but we just built a transducer. Although the origin of this word is often cited as the combination of “transform” and “reducer”, I like to think of the prefix “trans” in the context of reducers passing through one another.

Let’s modify all of our existing reducers to the new composable form:

Our data will now pass through each of these reducers and will be processed by the next reducer in the chain before being returned. Our nextInstructions could be a base reducer if we want to update the accumulator right away, OR our nextInstructions could be another one of these new composable transducers. Both take a runningTotal and a lemon as input, and both return a new accumulator, so we don’t have to differentiate between the two.

We can now build up arbitrarily long chains of instructions as long as a base reducer gets called at the end of the chain!

Keep in mind that data is not being reduced at this step. We are simply building up a single set of instructions that will be able to perform the reduction when the time comes.

Let’s reduce now:

Great! We can now compose together transducer functions. We originally wanted “a box of ripe yellow lemons, labeled by weight, with any underweight lemons removed.” Let’s make that happen.

Each new reducer gets its nextInstructions passed in when it is created:

We can build up our reducer in separate steps (top example), or create it all in one go (bottom)

Done! We have now reduced our lemon tree down to a box of ripe lemons, labeled by weight, with underweight lemons removed. And with only one trip through the lemonTree array!

One level deeper: a generic ‘filter’

You may have noticed that keepRipeLemon and keepHeavyLemon follow the same basic pattern. We return the result of calling nextInstructions only if the current lemon passes some test. Otherwise, we just return the current running total without modifying it. This is a pretty good sign that we can extract out that test as a separate step and make our composable-reducer-creator even more generic and useful:

This is getting pretty abstract. In fact, I still have trouble visualizing the exact path through the reducer from here (which is part of the reason I’m writing this post!). Let’s take a moment to trace through what’s happening:

Our filterInstructions first expects a tester function called test that returns true or false based on the lemon we pass to it. If test(lemon) returns a truthy value, we’ll set newRunningTotal to be the result from the next step in the instructions. If test(lemon) is falsy, newRunningTotal will remain the current runningTotal.

Returning the current runningTotal without calling nextInstructions is the equivalent of saying “here’s your bucket back” or “here’s your tally sheet back” without altering it.

“I only keep yellow lemons, but you gave me a green one, so here’s your empty bucket back”

When we pass in our test function to filterInstructions, we are returned a function that takes a nextInstructions reducer as an argument, which will either be a base reducer or another one of our custom reducers. Once we provide nextInstructions, we are finally returned a function that has the standard reducer signature.

This function is now “loaded up” with the ability test a currentValue, and to hand off further processing to the nextInstructions if the test passes.

A generic ‘map’

We can do a similar process to extract the “weighing and labeling” logic from our weighLemon reducer. Transforming a value by passing it through some function and capturing the result is called “mapping”, so we’ll call this function mapInstructions:

Again, let’s step through. Our mapInstructions function first expects a mapperFn. We’ll create a mapper function that transforms a lemon into a lemon-with-weight-label. Once we pass it our mapperFn another function is returned, which expects some nextInstructions as its argument. We’ll use our trusty old putInBucket. Finally, we’re ready to reduce.

Now that we have these final transducers, we can compose to our hearts content. Here’s what our final version looks like:

Same result: ripe, heavy, labeled lemons in a bucket. But much more reusable!

Reducing Redux state

Before we part, let’s take a quick look at Redux, since it’s likely the place we’ll be encountering reducers in action. Manipulating state in a Redux reducer is no different than reducing over our lemon tree. We provide instructions for combining an accumulator (the state of our redux store), and a current action (an object with a type property). The value we return will become the new state of the store, and will be used as the accumulator the next time the reducer is called.

Redux reducers often use a switch statement to determine which instructions to follow:

I won’t go into detail here since we’ve already covered so much ground. But other than a few implementation details, it should look pretty familiar!

Summary

Reducers run the gamut from “dead simple” to “brain-numbingly hard to visualize”, especially once composition enters the picture. Try to remember that a reducer is nothing more than instructions for combining two things: a running total, and a current value.

And also, how on earth did I go this whole post without reducing my lemon tree into lemonade?? Show me how you’d do that in the comments!

Notes:

  • I didn’t talk about “pure functions”, but they’re important in making sure we get the results we expect and are a foundation of functional programming. Learn about them!
  • Check out Kyle Simpsons Functional Light Javascript and this Egghead.io lesson for a more in-depth discussion of transducers and other functional programming concepts.

Async builds high performance, reliable, and cost-effective applications by combining technical expertise and deep knowledge of industry trends.

For more information on development services, visit asy.nc

--

--