In the previous post we’ve looked at three very useful functions: forEach, map and filter. Let’s now look at reduce - a bit more complicated function which shows the real power of functional programming.
I will describe to you a real-world problem which my colleague was dealing with at work. He was working on a filtering mechanism where the user could define conditions on a column in a table. The column would usually be mapped to a property of an object. However, it should also support nested properties. At some point in his code, he had to solve the following problem: given a string representing the path of nested properties in an object, return the value of that nested property. We can’t assume anything about the length of the path - properties can be arbitrarily nested. For example, given path author.address.city.size and object:
This function should return “large”. As usual, let’s first approach the problem in a traditional, imperative way.
var path = "author.address.city.size";
Firstly, we split the path so that instead of a single string, we deal with an array where each element is a single property in consecutively nested object. Next, we initialize a helper variable current to the object which we wish to inspect (book in our case). This variable will store the currently nested objects as we descend deeper and deeper inside the object. We iterate over the path parts and for each part we use it to go one level deeper. Once we are done, we end up with the desired value. And here comes the functional version:
pathParts.reduce((current, currentPart) => current[currentPart], book);
A single line! Isn’t it awesome? Ok, I cheated a bit by omitting some code, but it’s still one line versus four.
With previous higher-order functions (forEach , map and filter) we passed a function to be applied on each element. This time it is a bit different. We pass a function which takes two arguments - an accumulator and the currently processed element. What is accumulator? It is a value which represents the intermediate result of processing. Reduce looks at consecutive elements of the provided array. The accumulator should always hold a valid result for all elements processed so far. In other words, accumulator accumulates the results of processing consecutive array elements.
It’s easiest to understand when compared with the imperative code above. The current variable is actually an accumulator. For every loop step, we take the current level on nesting (stored in the accumulator) and use it to get to the next level of nesting. Then, we store the result as the new accumulator. Reduce follows exactly the same process but it hides the details of looping and initializing the accumulator. Let’s have a deeper look at how reduce should be called. It takes two parameters:
- reducer function which transforms the current accumulator value and an array’s element to next accumulator - it encapsulates the loop step in the imperative code where we did exactly the same thing - processed the next array element and produced a new accumulator
- initial accumulator - because we need to start with some value of accumulator (as in the imperative code, where we initialize current = book)
Let’s have a look at another example in which we will use reduce in a different way. Our use case is much simpler now: take an array of numbers and return a sum of its elements. Pause for a moment now and try to come up with a way to use reduce to solve this problem. Let’s try to figure out what should we store in the accumulator. I’ve already said that for every step the accumulator should store the valid result for the currently processed array elements. In our case the result is the sum of currently processed elements - that’s exactly what we will store in the accumulator. Next, let’s see what the reducer function should do.
As we know, it should take the current array element and the accumulator and produce the new accumulator. Since we agreed that the accumulator will store the sum of elements processed so far, then in order to produce a new accumulator we simply need to add the current array element to the old accumulator! Finally, the initial accumulator value. Since we’re going to add array elements to it, so it’s best to initialize it simply to 0. Wrapping it up, this is how our reduce call should look like:
var numbers = [1, 2, 3, 4, 5];
In this article I’ve explained one of the most powerful concepts of functional programming - the reduce function. Reduce has abundance of applications. Redux is based exactly on this concept. In Redux, we write reducer functions which take the current state and an action and produce a new state. Does it sound familiar now? I will dedicate a separate episode of this course to Redux, so stay tuned. Another great example is the MapReduce programming model used in parallel processing of large data sets. I hope I’ve got you at least a little bit excited about reduce :-)
If you have any issues understanding anything in this post or if you simply would like to provide feedback, please leave a comment below. I want this course to be as good as possible and I need your help for that! If you found this post helpful, please consider sharing it on Facebook or Twitter.