DEV Community

Davide de Paolis
Davide de Paolis

Posted on • Edited on

What s wrong with Array.reduce ?

We use XO for our code linting. Recently I upgraded to its latest version and suddenly I had lots of errors as soon as I tried to commit (**).

What was wrong?

Well. It seems that there is a new trend out there.

Array.reduce is the new most hated guy.

No use for Reduce tweet

It is disliked so much that a new ESLint rule was added to prevent - or reduce its usage.

What the heck!

I remember that when I started using it 3 years ago, it took me some time to understand the use case and find it cool and useful. And now, even though I don´t use it so often, it generally makes the code look quite nice and smart. Until now, I guess.

tableflip

When I found all these eslint errors I was quite pissed, first because they were unexpected and I did not want to spend time fixing my code, nor cluttering it with eslint-disable comments to ignore it. But I was also quite intrigued by the reasons behind this opinionated choice from AVA contributors.

I read some of the comments in the thread and started reconsidering the snippets in our repository that contain Array.reduce.

thinking

Let´s consider this simplified example, where we have a list of records and we want to validate them and aggregate all the valid and invalid ones.



const isValid = (record) => // run some validation logic over the record props and return true or false


module.exports.analyzeResults = (records = []) => {
     return records.reduce(
         (acc, current) => {
           if (isValid(current)) {
                  acc.valid.push(current)
             } else {
                 acc.invalid.push(current)
             }
             return acc
         },
         {valid: [], invalid: []}
     )
}


Enter fullscreen mode Exit fullscreen mode

With Array.reduce we can achieve it quite nicely, with one iteration only over the list and returning 2 new arrays.

What would be the alternative without Array.reduce and using Array.filter and Array.map instead, to still be as functional as possible?



module.exports.analyzeResults = (records = []) => {
    const valid = records.filter(r => isValid(r))
    const invalid = records.filter(r => !isValid(r))
    return {valid, invalid}
}
```

I know already what you are going to say:

> Ehi, but you are iterating over the list twice!!

True.

But the code is undoubtedly simpler and nicer to read.
So to some extent is the same objection many devs still say when it comes to use {% raw %}`
array.map(simplifyDataStructure).filter(bySomeProp).map(extractOnlySomething).filter(whatIwant)`
against doing everything in one single For Loop.

> Readability and Testability of the single operations

So unless you have a very very big dataset, it is really better to favour readability or simplicity rather than stuffing everything in a _complex_ reduced method.

I am not entirely sold on the new trend. And I am not going to rewrite all my methods using Array.reduce,  but this discussion really tickled my interest and helped me question my stance and coding.

What do you think?
---

(**)
> ProTip:  Use [Husky](https://www.npmjs.com/package/husky) to create a git hook so that whenever you try to commit XO is run and your code is linted.  that really helps to enforce coding standards in your team, and prevent unnecessary pickiness during code reviews.
Enter fullscreen mode Exit fullscreen mode

Latest comments (81)

Collapse
 
pengeszikra profile image
Peter Vivo

Reduce is fine in my view. A little bit tricky, but you can solve many problems. Main meanings: const result = something.reduce(reduceProcess, startingValue).

for example pipe function:

export const pipe = (inner, ...outers) => outers.reduce((result, calc) => calc(result), inner);
Collapse
 
zapbampow profile image
Clayton Ingalls

A number of people have mentioned "good" and "bad" uses of reduce. I'd love an example of what a good and a bad use would be.

I love reduce. My coworkers don't. I've always assumed the difference is a matter of familiarity with it. But I recognize that some of my code using reduce has been a pain point to some of my coworkers. So I'm working to use other solutions, even when I would personally prefer to use it.

As a simplified example, we might use these different solutions which use about the same amount of code and only loop once.

const original = [
    { a:1, b:2, c:3 },
    { a:4, b:5, c:6 }
]

// Solution needs to return [ { a: 1, b: 2 }, { a: 4, b: 5 } ]

const loveReduce = original.reduce((acc, cur) => {
    const obj = {
        a: cur.a,
        b: cur.b
    };
    acc.push(obj)
    return acc;
}, [])

let notReduce = [];
original.forEach(item => {
    const obj = {
        a: item.a,
        b: item.b
    };
    notReduce.push(obj);
});
Enter fullscreen mode Exit fullscreen mode
Collapse
 
vonheikemen profile image
Heiker

I can talk about the "good use case" a little bit.

Let's start with the classic, adding numbers.

const array = [1, 2, 3, 4];
const add = (one, another) => one + another;

array.reduce(add);
// => 10

There is a pattern here that is useful. If you squint your eyes you'll see that we have an array with a single data type (Number) and we have binary operation that works with that data type (add).

That simple pattern can be applied to more complex data types. Let's make an example with nested arrays.

const array = [
  ['hello'],
  ['awesome', 'hello'],
  ['!!', 'world'],
  ['!!']
];

const intersection = (one, another) => {
  const set = new Set([...one, ...another]);
  return Array.from(set);
};

array.reduce(intersection);
// => [ "hello", "awesome", "!!", "world" ]

In here we create a new array with the unique elements of each array.

Notice how in the functions add and intersection don't mention anything about an accumulator or a currentValue? That's the power of reduce. I guess you can say that it can "increase the capacity" of a binary operation. This in my opinion is the best use of reduce.

Now, don't take these example to the letter, I'm not trying to say these exact situations are a good use of reduce. I'm trying to show a pattern where reduce can shine.

I talk about this in more detail in this article.

Collapse
 
avalander profile image
Avalander

This seems a prime candidate for map though.

const result = original.map(({ a, b }) => ({ a, b }))
Enter fullscreen mode Exit fullscreen mode
Collapse
 
alaindet profile image
Alain D'Ettorre

What is better, using some native language syntax or calling a function for the same thing? I mean, in the following example maybe it's even easier to use reduce, but in my experience NOT using it is usually much easier.

const someStuff = [42, 69, 123, 10, 56, 9, 4, 3];

const theSum1 = someStuff.reduce((total, num) => total += num, 0);

let theSum2 = 0;
for (const num of someStuff) {
  theSum2 += num
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
dvddpl profile image
Davide de Paolis

i guess that´s the whole point. internet is full of article explaining how to use reduce. it´s new, it´s cool, and everybody wants to use it. even if they end up, ( me included, probably) misusing it.
( but yes, you can configure the linter to exclude the error, or just show it as warning)

Collapse
 
savagepixie profile image
SavagePixie

Math.max(...nums) for the win

Collapse
 
okslutsiv profile image
Oksana

A solution in the spirit of the latest trends. Who does not want to go up requires the rest to go down

Collapse
 
pentacular profile image
pentacular

This isn't a very good use of reduce, in my opinion, in any case.

In almost every case where you have an inlined reduction function, you're better off using a for loop, because it is less complicated.

const valid = [];
const invalid = [];
for (const record of records) {
  if (isValid(record)) {
    valid.push(record);
  } else {
    invalid.push(record);
  }
}

Simple, stupid, and gets the job done.

Your use of reduce just adds extra rubbish in the way, and is really a kind of abuse of a reducer in any case -- you're destructively updating it in-place -- so why not just do that in a more obvious fashion?

However, had you defined a reusable reducer, would have been a bit different.

const categorized = records.reduce(toValidAndInvalid);  

Now it's starting to look much more reasonable.

And let's get over this "in a functional way" nonsense.

array.reduce isn't functional -- it's just an application of procedural abstraction, there's nothing functional about it.

At best you can say that it's procedural code being used in a 'functional style', except that, really you can't say that, because you're not using it in a functional style.

So it's a bit like building a run-way in the jungle in hope that presents will magically drop out of the sky. :)

Collapse
 
functional_js profile image
Functional Javascript

Correct, no function on its own is "functional" programming.
"Functional" is only when a function is useable in a composable manner, meaning it can be composed with other functions to make larger functions.

For example, (and somewhat ironically), the "reduce" in python can be used functionally (it may have to be curried first), but not the "reduce" in JavaScript, because in JavaScript it's not a "free-function", it is bound as a member method of the Object blueprint, Array (essentially a Class). Thus in JavaScript's case, it's reduce "method" is Object Oriented. Also, a function that takes a function as a param doesn't simply make it formally "Functional Programming"

Of course you could make it a functional-programming-usable function by wrapping it in a free-function.

For example...

/**
@func
a functional reduce

@typedef {(acc: *, val: *) => *} reduceCallback
@param {reduceCallback} fn
@param {*} startVal
@return {(a: *[]) => *}
*/
const reduce = (fn, startVal) => a => a.reduce(fn, startVal);

//@tests
const p1 = pipe(
  func1,
  func2,
  reduce((acc, n) => acc += n, 0),
);
p1([1, 2, 3, 4, 5]);
Collapse
 
pentacular profile image
pentacular

Correct, no function on its own is "functional" programming.
"Functional" is only when a function is useable in a composable manner, meaning it can be composed with other functions to make larger functions.

Can you give an example of a function that cannot be composed with other functions to make a larger function?

For example, (and somewhat ironically), the "reduce" in python can be used functionally (it may have to be curried first), but not the "reduce" in JavaScript, because in JavaScript it's not a "free-function", it is bound as a member method of the Object blueprint, Array (essentially a Class). Thus in JavaScript's case, it's reduce "method" is Object Oriented. Also, a function that takes a function as a param doesn't simply make it formally "Functional Programming"

Putting aside the issue of javascript functions being procedures.

reduce isn't bound as a member -- you can use it independent of Array, and you can supply any 'this' you like when you invoke it.

Effectively there is an additional argument, which provides the value of this.

I think perhaps you are trying to understand javascript in C++ terms?

Of course you could make it a functional-programming-usable function by wrapping it in a free-function.

Javascript doesn't have a distinction between member-functions and free-functions.

And even if it did, being associated with a class is not in-and-of-itself relevant to being a function or functional.

For example...
const reduce = (fn, startVal) => a => a.reduce(fn, startVal);

All you're doing here is some syntactic hopscotch -- it's calling array.reduce normally, which should tell you that you haven't changed anything fundamentally.

But it's getting clearer that what you consider to be functional is just one particular kind of procedural composition.

What's significant about functions is that they're time invariant.

Collapse
 
vonheikemen profile image
Heiker

array.reduce isn't functional -- it's just an application of procedural abstraction, there's nothing functional about it.

Oh but it is. Have you ever heard about Foldable?

Collapse
 
pentacular profile image
pentacular

If it were, you wouldn't be able to go around pushing things in reducers.

As it is, it's just an example of higher order procedure application.

Thread Thread
 
vonheikemen profile image
Heiker

You did read the thing, right? If you didn't, please don't let the name stop you.

Thread Thread
 
pentacular profile image
pentacular

I read it.

Do you understand the difference between functions and procedures? :)

Thread Thread
 
vonheikemen profile image
Heiker

Function like in the mathematical sense? Sure, I like to think I do.

Collapse
 
robdwaller profile image
Rob Waller

This strikes me as overly opinionated. I think it also suffers from 'senior dev syndrome', where the reasoning will be more obvious to experienced developers than inexperienced ones. So it won't necessarily result in the use of nice, clean maps and filters, but possibly nested loops which could be even worse.

Collapse
 
ryands17 profile image
Ryan Dsouza

I don't think reduce is bad, it's just the way some use it to make it more complicated than it should be. Those same things can be done easily with a for loop and with the inclusion of the for ... of loop, it becomes quite easy to perform those operations.
Jake actually has a video for the same and you can see that how simple use cases are over engineered with reduce.
I also agree with the point that reduce has its parameters reversed, the callback should always be last, but that's a personal opinion.

Collapse
 
baukereg profile image
Bauke Regnerus

I love reduce. Screw eslint. As long as you don't use an inline callback method, reduce is awesome.