Russian translation: Уменьшение в JavaScript
When interviewing candidates,
I used to ask them about several native methods of Array in JavaScript
they had used and let them implement one of the methods on paper,
such as some
or map
.
If someone knows that functions can be passed around,
this should not be a hard task.
The reduce
function was most rarely mentioned.
The discussion
Last week Sophie Alpert tweeted about
her rule for the reduce
function. There was then a hot discussion,
as the last two rules in the list were controversial among many people.
my rule for .reduce(): only use it when the result has the same type as the items and the reducer is associative, like
— Sophie Alpert (@sophiebits) February 22, 2019
.reduce((a, b) => a + b, 0)
✅ summing some numbers
✅ multiplying some numbers
🚫 building up a list or object
🚫 just about anything else (use a loop)
I myself use this function a lot. Not only for summing and multiplying numbers, but also for composing a new array or object. Sophie's rule made me think about this function again.
Reduce vs Fold
Reduce
is a higher-order function which is usually called fold
in functional programming languages.
For me, fold
is definitely
a better name compared to reduce
,
since this's how it behaves like folding a fan in my head.
It also has directions, reduce
traverses a list from left to right,
reduceRight
does from the other direction.
You've probably seen foldl
and foldr
somewhere else,
they're the same thing but much readable.
In JavaScript, if no initial value is supplied to the reduce
function,
the first element in the given array will be used.
That's why I prefer foldl1
or foldr1
in other languages like Haskell,
which explicitly uses the first or last element as initial value.
Two tasks
There are two simple tasks by given the following data, and solutions with normal loops:
a) Get the total score of all items.
b) Get the item with the highest score.
Seeing patterns
The above two functions have something in common:
- An initial value.
- A rule on how to update the initial value with each item in the list.
- Returns the final updated value.
We can define a function to abstract the repeated pattern.
This is actually a simple version of Array.prototype.reduce
.
Now rewrite the solutions to see how they will look like with reduce
.
Once you identify this pattern, the code will be more understandable. The benefit of the abstraction is that the rule can be separated and possibly be reusable.
The difference
The above getScoreSum()
and getHighest()
also have a different place.
getScoreSum()
returns a number.getHighest()
returns an object, the type is same with items in the list.
Normally it's called asymmetrical in its types when the type of the returned value is different from the type of items in the list. Otherwise, it is symmetrical.
The first two rules in Sophie's tweet basically referred to symmetrical functions.
Sometimes it's convenient to make the first element as the initial value when it's symmetrical.
Also, we could reduce one iteration in the loop. So here are little modifications to the reduce
function we just defined.
Much simpler now:
Building up a list
A good use case of building lists with reduce
is the implementation of the flat
or flatMap function.
With the normal loop:
Again, we see the pattern exposes.
So let's try to do it with reduce
.
As you can see it's not that bad. We can go further to support flattening on deep nested array:
Other examples
Some other functions can also be implemented with reduce
easily.
We can do this as a practice regardless of some performance concerns.
Thoughts
Abstractions are ways of thinking.
If the performance is not crucial I still prefer to use reduce
function whenever appropriate.
Because it allows me to focus on the most important part and write less code.