DEV Community

Joe Pea
Joe Pea

Posted on

You don't need Array.reduce()

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!

Latest comments (30)

Collapse
 
dei8bit profile image
§dei§

I'm happy because as soon as I entered the post I saw this sentence:

but I didn't feel convinced about using Array.reduce().
And honestly, I came up here because I think reduce is a really bad method in terms of readability :p
And I asked myself, are there better alternatives? Because if there are, I never want to use reduce again in my life. (hope this)
If there are: great, I'll learn them.
If there aren't: okay, I'll make an effort to learn it. It's a win-win.
Now to read the post xD, I don't even know what it's about yet.

Collapse
 
dei8bit profile image
§dei§ • Edited

okay that was very fast xD i love it for of instead of ugly, unintuitive , larga, heavy, and don't funny reduce method. match forever, sorry reduce i hope never see you again.

i found as a cons:
you can't chain methods... or interrupt a possible chain of methods with a for of

as a pros:
legibility
performance

again prefer for of of course

Collapse
 
conaxliu profile image
ConaxLiu • Edited

I haven't seen any mention of forEach in this thread. So I wonder why do we still need reduce when forEach can achieve exactly the same result and number of code lines?

var trips = [{type: 'car', dist: 42}, {type: 'foot', dist: 3}, {type:'flight', dist: 212}, {type: 'car', dist: 90}, {type: 'foot', dist: 7}] 

var distanceByType = {};

trips.forEach(curr => {
    const { type, dist } = curr;
    if (distanceByType[type]) {
        distanceByType[type] += dist;
    } else {
        distanceByType[type] = dist;
    }
});

console.log(distanceByType);
Collapse
 
trusktr profile image
Joe Pea

Totally! And that is also much more readable!

Collapse
 
jtenner profile image
jtenner

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.

Collapse
 
trusktr profile image
Joe Pea

Good point. Thanks for that perspective!

Collapse
 
enriquemorenotent profile image
Enrique Moreno Tent

Your first example can be solved like this:

add = (a, b) => a + b
array.reduce(add, 0);
Enter fullscreen mode Exit fullscreen mode

Your second example can be solved like this:

getDist = (obj, type) => type in obj ? obj[type] : 0

addTrip = (acc, trip) => ({ ...acc,
    ...{[trip.type]: (getDist(acc, trip.type) + trip.dist)}
})

trips.reduce(add, {})
Enter fullscreen mode Exit fullscreen mode

Both example look easier with reduce, I think

Collapse
 
ivanperez profile image
Iván Pérez

You are creating one object per iteration! This can't perform well with large arrays...

Collapse
 
trusktr profile image
Joe Pea

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.

Collapse
 
qm3ster profile image
Mihail Malo

It also helps a lot when one has a phobia of statements.

const panic = err => {
  throw err // oh no, a statement!
}
const apply = (state, event, context) =>
  (this.reducers[event.type] ||
    panic(new Error(`Unknown event type: ${event.type}`)))(
    state,
    event,
    context
  )

const applyAll = (state, events, context) =>
  events.reduce((state, event) => apply(state, event, context), state)

const replay = (events, context) => applyAll(this.initialState, events, context)

Collapse
 
qm3ster profile image
Mihail Malo
let sum = 0

Ah, but now the sum is mutable! Who knows what will happen to it!

Collapse
 
seniorjoinu profile image
Alexander • Edited

Seriously, man? for..of?

const myTotalLikes = retrieveAllPosts()
    .filter(p => p.user == me)
    .map(p => p.likes)
    .reduce((acc, like) => acc + like, 0)
Enter fullscreen mode Exit fullscreen mode

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.

Collapse
 
lewiscowles1986 profile image
Lewis Cowles

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

function totalLikes(user) {
    return likes = retrievePostsFor(user)
        .map(p => p.likes)
        .reduce((sum, likes) => sum + likes, 0);
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
lewiscowles1986 profile image
Lewis Cowles • Edited

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.

Collapse
 
andrewzhurov profile image
Andrew Zhurov

Is this correct?

const pipeOnce = (fn1, fn2) => (args) => (fn2(fn1(args)));
const pipe = (...ops) => ops.reduce(pipeOnce);

It seems for me pipeOnce should be

(acc, fn) => fn(acc)

Though it would not be 'pipeOnce' by semantic anymore:)

Collapse
 
trusktr profile image
Joe Pea

Heh, well this shows that thinking about it is more complicated than the for..of loops.

Collapse
 
andrewzhurov profile image
Andrew Zhurov

Mutation transfers a var X from state A to state B, so now, when we intend to use X we need to check is it A or B.
We actually do mutations for 'complex behaviour' - ability to launch different execution branches for the same input signals, which to launch is determined by current state of X. In such case X would be 'control state' (FSM term).
It is the appliance where it's truly needed, others are adding accidental complexity to simple data/value transformation.
Thanks for the post, got me thinking

Collapse
 
nicowernli profile image
Nicolás Wernli

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);

Collapse
 
theodesp profile image
Theofanis Despoudis

Actually, this is better as you can extract the reducer part
(a, n) => a + n as a function. The other way is not reusable.