I was reading another dev.to post, Demystifying Array.reduce()
, but I didn't feel convinced about using Array.reduce()
.
Maybe I too am not using Array.reduce()
the right way, but every time I do, I end up disliking it and switching to a simple for..of
loop.
Up ahead are the three examples from that article, converted to use for..of
and in my opinion easier to read and cleaner.
Take for example the sum example:
const array = [1, 2, 3, 4];
const sum = array.reduce((accumulator, currentItem) => {
return accumulator + currentItem;
}, 0);
// sum = 10
It can be written as
const array = [1, 2, 3, 4]
let sum = 0
for (const n of array) sum += n
// sum = 10
That's simpler!
The next example,
const trips = [{type: 'car', dist: 42}, {type: 'foot', dist: 3}, {type:'flight', dist: 212}, {type: 'car', dist: 90}, {type: 'foot', dist: 7}]
const distanceByType = trip.reduce((out, curr) => {
const { type, dist } = curr;
if (out[type]) {
out[type] += dist;
} else {
out[type] = dist;
}
return out;
}, {});
// distanceByType = {car: 132, foot: 10, flight: 212};
can be rewritten as
const trips = [{type: 'car', dist: 42}, {type: 'foot', dist: 3}, {type:'flight', dist: 212}, {type: 'car', dist: 90}, {type: 'foot', dist: 7}]
const distanceByType = {}
for (const trip of trips) {
const { type, dist } = trip
if (distanceByType[type]) {
distanceByType[type] += dist
} else {
distanceByType[type] = dist
}
}
// distanceByType = {car: 132, foot: 10, flight: 212}
Simple!
Finally, the example from the comments about piping functions,
const pipeOnce = (fn1, fn2) => (args) => (fn2(fn1(args)));
const pipe = (...ops) => ops.reduce(pipeOnce);
const addTwo = a => a + 2;
const mulTwo = a => a * 2;
const addTwoMulTwo = pipe(addTwo, mulTwo);
console.log(addTwoMulTwo(1)); // (1 + 2) * 2 => 6
console.log(addTwoMulTwo(2)); // (2 + 2) * 2 => 8
console.log(addTwoMulTwo(3)); // (3 + 2) * 2 => 10
is a better of example of reduce, but it can be written as
const addTwo = a => a + 2;
const mulTwo = a => a * 2;
const addTwoMulTwo = n => mulTwo(addTwo(n))
console.log(addTwoMulTwo(1)); // (1 + 2) * 2 => 6
console.log(addTwoMulTwo(2)); // (2 + 2) * 2 => 8
console.log(addTwoMulTwo(3)); // (3 + 2) * 2 => 10
If we want to pipe an arbitrary number of functions, we can do it with for..of
too:
const pipe = (...fns) => arg => {
for (const fn of fns) arg = fn(arg)
return arg
}
const addTwoMulTwo = pipe(addTwo, mulTwo)
This one isn't as short, but it is easier to understand.
What are some use cases where Array.reduce()
really shines over alternatives like for..of
?
Please share if you know!
Top comments (28)
One of the reasons I like using reduce is because it prevents accidental mutation of the the original array and it is consistent with how it works and looks (in fact this applies to the other array methods like map and filter).
for loops either mutate data or perform side effects, which as a general rule why they are good to avoid.
map
,filter
, andreduce
also allow better code reuse and testability because it can be broken down further than a for loop can.Example:
You'll also notice all those
let
s go away and becomeconst
.Get rid of for loops and it's possible to write your entire program without a single
var
orlet
!But if you don't pass any initial value then there would be errors in case of empty arrays. Example:
I think you mean
You need a seed value.
Nope. That is exactly what I meant.
The seed is optional. If you don't provide a seed, the first item in the array is given as the accumulator.
Run the code!
Sum is just about the only case where reduce is useful, but in reality it should never be passed around like that. Why keep an
add
function around that is only meant to be used together with areduce
? Just wrap it once and never use reduce again:Don't tell me
arr.reduce(add)
makes more sense thansum(arr)
because it doesn't.In reality you can write
sum
event more efficiently with a regular loop and every part of your code benefits.Seriously, man? for..of?
Sometimes it's better to use for..of (e.g. when you need some boost iterating over a huge collection you can do all the stuff in one loop) but functional style is much more useful when you are trying to express yourself to your colleagues.
My head nearly exploded looking at that. Map reduce is not a good pattern for application engineering, it's a backend big-unstructured-data pattern.
retrievePostsFor(me)
would be so much easier to think about as well. Baking in getting all posts and filtering is just not clear unless you have a shallow micro-service code-base, embrace the cascade.Finally wrap that up in a method
to anyone liking this, it works better if you just send in users. Then it doesn't matter where they come from, and anything with 0 likes just adds items to the list...
In-fact it doesn't need to be a user at all. Just something implementing a likeable interface, which has a method to retrieve likes.
I'd agree maybe it's that I'm used to for loops but they read easier for me and I know better what's going on.
I'd also say it's the same for the majority of JS users (at least from my experience) so when you got a bigger team the safer bet is to use for loops. Helps to make the code easier to understand for more people.
I would say that the absence of side effects is often overvalued when talking about this. Though I understand where people are coming from :)
Your first example can be solved like this:
Your second example can be solved like this:
Both example look easier with reduce, I think
Those are shorter, but at a glance it still takes me more time to understand it than the for..of loops. Maybe it's just how my mind is trained.
You are creating one object per iteration! This can't perform well with large arrays...
I'm guessing map, filter, fold, reduce etc were born from adopting functional programming methods. Sounds like you're not really into that style (while I totally agree about lack of side effects and mutation). It sounds kind of weak, but I would say to truly get to the heart of your question, work with some Haskell or purescript (or look into lambda calculus), instead of trying to compare JavaScript code.
Maybe ReasonML could help too, because it's closer to JavaScript.
First example could be simpler with reduce, I mean, if you use n as variable in the for version, why not using n in the reduce one?
const array = [1, 2, 3, 4];
const sum = array.reduce((a, n) => a + n, 0);
Actually, this is better as you can extract the reducer part
(a, n) => a + n
as a function. The other way is not reusable.I use
Array.prototype.reduce
a lot. The reason why is because v8 and firefox does a very good job of optimizing reduce calls when it gets optimized.Often in the case of clarity over speed, using
for..of
loops is the best. Sometimes, it's not always about clarity.Good point. Thanks for that perspective!
Is this correct?
It seems for me pipeOnce should be
Though it would not be 'pipeOnce' by semantic anymore:)
Heh, well this shows that thinking about it is more complicated than the for..of loops.
Keeping the functional programming jargon aside, reduce might be difficult to read for people familiar with imperative style of programming. You need to focus on the part that reduce reduces the cognitive load from your head (of course after you understand how it works). You will appreciate that all you need to care about is the function that you pass to the reduce. There is not outer references that you need to be bothered about. You can even move this function to a new file and it would make sense to any fellow developer( It’s called code decoupling), because everything is self contained! no need to worry about some variable in the outer context. You can google more about the benefits of code decoupling.