loading...

What´s wrong with Array.reduce ?

dvddpl profile image Davide de Paolis ・3 min read

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: []}
     )
}

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
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 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.

Posted on by:

dvddpl profile

Davide de Paolis

@dvddpl

Sport addicted, productivity obsessed, avid learner, travel enthusiast, expat, 2 kids. 🏂✈🚞🌍📷🖥🤘👨‍👩‍👦‍👦🚀 (Opinions are my own)

Discussion

markdown guide
 

Nothing. Most people are more familiar with imperativ or object orientated programming, so so functional programming practices are unfamiliar. Unfortunately for some developers unfamiliar automatically means it's wrong.

 

This about sums it up.

Once people are familiar with reduce, it looks just like any other boiler-plate. The callback function used is the "reducer". I would find it weird if I saw React people preferring the old for-loop.

I think the test-ability argument is a moot point in the thread, because if there are many conditions and a really fat reducer function, that needs testing, not the reducer in the context of a reduce invocation.

An argument can be made on both grounds. Functional AND declarative code is typically preferred. A lot of people have already used this example but in a less readable fashion, but let's really illustrate declarative code:

// simple to unit test this reducer
function maximum(max, num) { return Math.max(max, num); }

// read as: 'reduce to a maximum' 
let numbers = [5, 10, 7, -1, 2, -8, -12];
let max = numbers.reduce(maximum);
 

Calculating lcm of more than two numbers also becomes impressively simple and beautiful. For example my AoC day 12 solution.

function gcd(a, b) {
  if (!b) return b === 0 ? a : NaN;
  return gcd(b, a % b);
}

function lcm(a, b) {
  return (a / gcd(a, b)) * b;
}
const systemPeriod = periods.reduce(lcm)
 

I like this approach (plus I finally understood where reducer comes from :D - in .NET it's called Aggregate). However, I also think that if one is not going to (re)use multiple types of reducers, there is no need to use reduce to emulate a forEach or even a normal loop.

But congratulations on the conciseness of your code!

Yeap! I've never done C#, but the analogs are listed in this SO thread: stackoverflow.com/questions/428798...

Yeah, it was a contrived example from many people in this thread. I also don't use JS regularly at all anymore which is why I often default to ES5 syntax.

I typically end up using reduce for "partitioning" or performing only a single iteration through the collection to include/exclude/filter into a different data type, like a set, map, etc to keep things atomic... we've all seen people declare combination of multiple arrays, array and object, array and map, etc at the TOP of the file and the actual for-loop is modifying it 100 lines down, and some other code that modifies it in between... that's really really really bad and shitty for anyone needing to debug it. These functional facilities over collections make them atomic and you know where to look if there's a bug -- the reducer (hopefully unit-tested)...

I've used reduce for a lot of this (changing data-types, and accumulation to a different collection type)

let numStrings = ['1', '2', '3'];
numStrings.reduce((map, num) => {
  const num = parseInt(num);
  return num % 2 === 0 ? map.evens.push(num) : map.odds.push(num);
}, {evens: [], odds: []});

// RESULT (strings are converted to integers in a nice map/object of evens and odds)
// {evens: [2], odds: [1, 3]}

Some people call this "partitioning". The above would be difficult to express with just a filter call or for-loop without scattering variables. The thing that gets most people is where the "initial value" {evens: [], odds: []} is. People are just very used to seeing it right above an iteration

I see what you are saying, but unless you are going to reduce that function, this can just as well be expressed by a forEach or a loop. One declaration of a variable above it doesn't bother me. Cheers!

forEach just abstracts away the iterator variable boilerplate, but could still leave the collection variables scattered about, like at the top of the file and make it possible for someone to mutate them in between... but yah!

Pardon for jumping in but fold are very well studied functions, as you know :) Sorry for dropping a link, but it speaks much better than I could: Fold Higher Order Function

@wulymammoth your code is slightly wrong. You have to return the accumulator (the object of arrays, this case). You're instead returning whatever push returns (which is the length of the array after pushing). (Also, you're re-declaring the parameter num as a const, which isn't allowed, but that's a simpler fix.)

 
let max = Math.max(...[5, 10, 7, -1, 2, -8, -12])

Yeap! Just re-using the maximum example that others have used for illustration purposes only. JS already has this available in the standard library :)

 

Exactly! I reckon anything overused is bad. However to straight up deface the enormous power a fold function gives is just wrong itself. Structural transformations are a thing, and there's a tool for them, period.

 

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

There is nothing wrong with reduce, it's only sin is to be slightly more difficult to understand than a for loop. The arguments against reduce can be reduced to this phrase.

I can do this with a for loop.

That's it. Anything that you can do with reduce you can do with a for loop, but since the for loop is the "simpler more readable" choice then it must be the right one.

 

In this case simpler and more readable is very subjective.

 

Is it not safe for us to say that once you understand how the reduce function works, then reading/understanding it becomes quite easy.

I agree that the loops are more readable though but I don't think it's enough to tell folks 'never' to use reduce

 

That's why it's wrapped in quotes.

 

Agree. Readability is anyway mostly a matter of being used to read such code. :-)

 

mmm. but this is true also for array.foreach and array.map etc. and i find those much nicer and simpler. I love reduce. but i kind of agree with the rant in the tweet to some extent

 

I'm just repeating the only argument against reduce I think it make sense. By some misfortune reduce doesn't seem to "click" with people in the same way map, filter or forEach do.

which is a shame, because map, filter, and forEach are just reduce in disguise.

Though, for completeness, all of those are just loops in disguise (which isn't an argument for or against it).

Only if you consider loops and tail recursion to be the same thing, which is debatable.

Also, it's only the same when applied to lists.

Other monadic structures may not act as loops at all, when called with reduce aka fold

 

I haven't used array.reduce enough to really have an informed opinion on that specifically. In my job I don't write core code, I merely maintain and review it after the engineers move on. 90% of the code I read isn't my own and in order to be cost effective I have to be able to read and understand what was written.

When I worked in video games, I can also remember a number of times where an engineer would leave and we'd have meetings pulling various engineers in to decipher some random piece of code. The succession plan of your code is greatly improved if it's simple and readable. I imagine Open Source products benefit in the same fashion.

From that perspective, it might not be just preference that influences the decision but a business decision to favor code that is simple and readable over complex and/or elegant.

 

I don’t mean this as an insult, but I don’t believe any developer goes in saying, “I’m gonna write cryptic, concise, and unreadable code”. As you can see reduce is not unfamiliar to a lot of people. It is unfamiliar mostly to those that haven’t explored functional languages. In fact, underscore.js and lodash.js provided these utilities before they were a part of the standard library in JS.

I would argue that it is never a business decision to favor code that is simple and readable. The business/org doesn’t care about the implementation details — it is the dev team that will constantly be reading it. So if the team is unfamiliar with a particular style, keep it consistent. It’s fair to argue that everyone knows the standard boilerplate that’s imperative with a for-loop. But for growth and maintainability, which can be orthogonal to “presently readable”, I’d challenge the constant use of for-loops.

Some people explore different languages so they can express things better, but do realize that — familiar != better. It’s subjective. Someone that doesn’t have access to a large vocabulary may need a whole sentence to describe something that could perhaps be described in a single word. This is what this essentially comes down to. I mention this because this has a real impact on source lines of code and for most people, less code == less maintenance, and less surface area to search across when a bug arises. So there is a preference from my obviously biased perspective for people to expand their “vocabulary” and then decide when the right time or wrong times to use something are.

Furthermore, I much prefer people to use the filter method on arrays rather than seeing a General for-loop that requires me to read the body of that loop to decipher that what it’s actually doing is filtering. Declarative > imperative

 

I didn't mean to imply that developers intentionally make there code complex, but the OP did mention that he initially favored array.reduce because it "looked smart".

The barrier to understand code isn't necessarily subjective. A for loop in most cases is objectively more widely understood than array.reduce.

If you work on a team where you aren't going to be responsible for maintaining your own code and you aren't in control of who will, then choosing a simpler more readable approach might be something you'd consider. From a business perspective the more time someone spends trying to understand what you wrote the more expensive what you wrote becomes which depending on the team you may or may not care about.

Okay, you’re making the same argument Objectively easier? No. I disagree. Familiarity, yes. Give the example of filter or reduce to a non-programmer and ask them what they think is happening. The for-loop is a familiar construct for those coming from C-style languages.

So you’re still arguing on the basis of “simpler and more readable”. It is subjective, because if I’m the audience, it is far simpler and more readable to me to see a reduction than have to read through the body of a for-loop to understand what’s happening. What you’re really stating is to consider writing in a way that covers the broadest audience.

Let’s take another concept that’s often hotly debated — recursion. Should we avoid recursion because we want to provide the most accessibility? Honestly we can just use for-loops and nested for-loops to express a bunch of ideas that are recursive or repetitive in nature. The fallacy in your stance is using “simpler and readable” because what is simple and readable is different to different audiences of different levels of programming ability. Therein lies why it is subjective. By golly if someone wrote a bunch of for-loops to express a recursive operation that is elegantly expressed in a simple recursive function with a base case would drive me nuts. The only consideration would be performance here — memo-ization and dynamic programming is far more performant and changes recursive functions to iterative and performant ones but is far more difficult to understand

Just to be clear, my original point was: If you're given a choice, simple and readable might be something you'd consider because there might be a business advantage.

//edit I also unfairly edited my response to you prior to you responding. Apologies for that.

"Consider your audience" is probably the point I was trying to make. Thank you for talking me through that.

Fair -- whatever "simple and readable" means to the developers...

But again, I, too, responded to that bit -- "business decision" or "business advantage". Unless you're in the business of writing code for developers to read, this is a bit of a moot point. Right? The business does not care about the code -- only the result of that code. The consumer of an iOS or Android app does not need to know whether the developer has used for-loops or reduce in the code...

Open Source might be an example of a situation where a single dev might consider there audience being other devs.

I also don't think it's fair to say businesses don't care about the code. I just don't think it's practical for most businesses to care. A business might empower someone to make decisions on their behalf; "Lead Developer" comes to mind. A business might document how it expects it's developers to code, Google's Style Guides for example. Either of those might care whether for-loops are favored over reduce.

I don't think many developers appreciate the succession of their code. I also think, many developers don't have to. If you're a single dev, writing a single app, for a small business who doesn't care about anything except the app being delivered on time. Your audience and priority is obviously not other devs.

Yes. Familiarity and audience are the key here. In our team everyone is able to understand a reduce. And everyone likes it over a forloop. But now that I rewrote it with filter and map it looks more readable than reduce. So it depends. I really like the conversations here. Thank you all

Communication in a team and outside both matter. It's always about the audience and sometimes that communication means getting each other on the same page. Filter and map are less generic than reduce when maintaining the same collection data type. Reduce is typically used to "distill" or bring it down or fold the collection (array) into something else. It's much more clear to map and filter in many instances because that's exactly what we're doing -- we still want to retain the collection (array). Whereas some other operations are: "reduce to an integer/sum/max", "reduce to a different data type/object", "partition" that are better expressed as reductions that map and filter can't do.

If it's a mapping operation or filtering operation, I'd much prefer to see map or filter. If I were reviewing that code and somebody threw a reduce to do what a map or filter call does, I'd ask them why...

But generally, I see folks throwing the for-loop at map and filter operations as well -- procedural and imperative. I'd much rather the code state what it's doing (by declarative means) than have me read through the body of the for-loop to determine that what is being done is a reduction, a mapping, or a filtering operation. The for-loop is much too generic outside of small use cases.

For one place that I've worked, we always asked people to use reduce, map, and filter. If a for-loop is used, that means it's a signal to other developers that there is the possibility of early termination (not iterating across the entire collection). So we have all these use-cases broken down and documented, otherwise the for-loop can be ANY of these four operations and requires the developer to read the body of the for-loop. Accessibility is high if the audience is broad, but in a team-setting with established patterns, this is just overhead...

Indeed. Early termination (or very huge datasets, where I really need/want to iterate only once) is the only case where I use for loops. Otherwise it's always map and filter + reduce sometimes.

I didn't mean "businesses don't care" to be a sweeping statement, but again, it comes down to the audience.

When you're bringing it into the arena of empowerment, it's almost an entirely different discussion. A lead developer or senior dev or engineering manager typically help bridge the gap between product managers/stake-holders which I perceive to be "the business". To me, that's divorced from the implementation as stakeholders at companies that I've been at have no say and won't even be able to read code. Even if you take Sundar Pichai and ask him, unless he's worked in a language, he's probably not going to have a valuable opinion on how some deliverable should be implemented.

When we're talking about style guides, these are created and consumed by developers and if you're in the business of building for developers (I currently work on APIs for devs), it is important that everyone is on the same page about these sorts of things. As someone that also contributes to documentation, I'd probably share both examples and let the end-consumer decide which is best in their case, but in the source code of the library, follow the established or agreed-upon style guide (if any) internally.

And you're totally right in your last paragraph -- I think this entire discussion started on the premise that our code is intended to be read by others and why reduce may be bad. But like any tool, it's just a tool and they can be used well or poorly. Someone stuffing a massive reducer as the callback to Array.prototype.reduce is a code smell, but so is stuffing that same conditional logic within the body of a for-loop. And if there are multiple declarations an initialization of collection objects (arrays, objects, sets, map) that the for-loop will be mutating scattered about on different lines, it's going to be very difficult to keep track and who knows if between those lines some other code is mutating it before it reaches the for-loop. Reduce keeps things atomic. There are a segment of folks that will tell you that the readability trade-off for atomicity is HUGE, because debugging won't be an issue with reduce as you're sure that mutations happen in ONE place. The easy thing to do doesn't always yield the desired result. The one thing that is constant in software is change -- the more churn and change that happens to such code will inevitably result in a regression that becomes hard to track down, fix, and/or refactor. Look no further than scripts. Scripts are ad-hoc and implicitly coupled to the very specific use case the author had in mind, but are not maintainable by a team of people. This is why patterns exist and abstractions and sometimes it is good to force the wider audience to learn and adapt to them if there is merit and that merit being less pain later on. It's not to "be smart". Those that are deeply down the functional paradigm can be assholes but unfortunately what they spout is too far to bridge the gap between what most people are used to and what they've come to realize (useful and arguably better). But I think core functional facilities like reduce strike a good balance and it's best that people are familiar with when and why to reach for it and also not be taken aback when it is used. Lastly, they should also call out when it is abused -- stuffing a massive callback/reducer. Just rip it out and give that a name and unit-test the reducer in isolation. It's not reduce's fault that someone decided to write an anonymous/lambda/callback that is untenable and unreadable. People are arguing about the wrong things here...

// this should be tested in isolation
function sum(total, num) { return total + num; }

// read as: "reduce to a sum"
let numbers = [1,2,3,4,5];
numbers.reduce(sum);
 
 

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. :)

 

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

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.

 

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?

 

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.

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

I read it.

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

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

 

The way I see it, it's mostly familiarity bias. reduce will be harder to understand to someone who is not used to it and, well, since anything that can be done with reduce can be done with a for loop, why learn a new construct?

Also, regarding your example, I think a better functional primitive to use here would be partition. Unfortunately, Javascript doesn't have partition out of the box, but it's a function that takes a predicate and a list and returns a list with all the elements for which the predicate evaluates to true and another with all the elements for which the predicate evaluates to false.

The implementation is straightforward with reduce:

const partition = (pred, xs) =>
  xs.reduce(([ t, f ], x) =>
    pred(x)
      ? [ t.concat(x), f ]
      : [ t, f.concat(x) ],
    [[], []]
  )

Or you can take it for instance from ramda.

And then your analyzeResults is just a one liner:

module.exports.analyzeResults = (records = []) =>
  partition(isValid, records)

This is to show that, while reduce is super useful, there are still a lot of functional primitives missing in Javascript, which forces people to use less elegant solutions, and might contribute to the perception of reduce being hard to read.

 

This!! Yes! I actually used reduce in JS and Ruby to partition in the past

 

very good point. thanx for commenting

 

I agree your example is one where reduce is better expressed in other ways. But I think there's cases where reduce is the correct metaphor and the clearest construction:

const max = (numbers) => numbers.reduce((max, number) => number > max ? number : max);

I will never be convinced that a for loop is more readable than that (and map and filter aren't options for this type of computation)

 
 

numbers.reduce((max, num) => Math.max(max, num), 0)

Math.max(...numbers)

Edit: also note that your implementation is incorrect. If all numbers are negative, the function will return 0 even if it is not in numbers. The starting value should be -Infinity.

Fair points, but it's not "wrong". My intention was to set a floor, though, not explicitly stated. The initial value matters. Although, your suggestion is the same as mine, because your floor is -Infinity. To derive the min, we don't need an initial value at all -- it's optional. The only instance in which that wouldn't work is an empty array - sort of the case I was covering :)

Now, let's write some unit-tests to document :)

 

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

 

I almost always to write first argument as prev or acc; therefore,

numbers.reduce((prev, current) => current > prev ? current : prev)

About readability, I guess it depends on how you visualize.

 

This seems like a very personal choice. I personally am a fan of reduce as it forces me to identity what I'll be "managing" over the loop. I also don't usually use many of the for loop alternatives, even though they are essentially just as capable.

A perk of JS is its flexibility, so there are multiple ways to do the same thing. As a side-effect, people will always have opinions on which is the right way.

If you like reduce, keep using reduce. If someone else hates reduce that isn't on your project, don't worry about it. If they are on your project, then decide the best way forward together :)

 

The problem with reduce is that there are two things happening in parallel. What happens to the accumulator and the function operating on the list. This situation always makes reasoning harder, at least until you've practised a bit.

I guess you can say a for loop has same issue with the iterator and the operations carried out but they are sort of separate things.

The reduce example in the article added a third piece of action, the choice between valid and invalid. To me that is too many moving parts for a functional approach and the latter example using two filters is better.

Yes it is slower with the two list iterations but if you need speed then use a for loop.

 

I really appreciate how you articulated your thoughts. This is how I see it too. Thanks

 

The JavaScript community continues to follow the trend of syntactical additions being requested, added, praised, and then criticized because of unreadability and mental overhead. If you want to use reduce because it fits your context and provides clarity, then use it. The problem is that too many people misunderstand why the convention was initially introduced — not to provide a "swiss-army knife" replacement for for loops, but to alleviate complexity and provide more purity when writing functional code.

 

There is definitely nothing wrong with reduce. And I'm sorry, but someone who finds reduce "unreadable" should not be programming linters.

 

Can't you just modify linting rules so that reduce is allowed?

Problem with reduce is that people are misusing a lot.

 

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)

 

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.

 

I think for loops are always king. However, reduce can actually be really nice in some cases, for example, you have an array of items in a cart and just want to list the total you can reduce the array and it looks really nice. I think reduce should be used in cases like this where you are just reducing an array down to something very specific like adding up all the numbers.

 

And it's better to make a semantically rich utility function.

/**
@func
sum up an arr of nums

@param {number[]} a
@return {number}
*/
export const sum = a => a.reduce((acc, n) => acc + n, 0);

sum([1, 2, 3, 4, 5]); // 15

P.S.
Although it might be tempting to extend the built-in Math namespace with a "Math.sum", that would be an antipattern to do so.

 

Good work Davide.

a.
Let me mention that to keep the use of reduce on it's semantic intention, pass in a list of elements of type T then return a single value that is of type T.

b.
I prefer a loop over a reduce simply for performance reasons.

In my performance tests, a loop will run about 10% faster on small arrays, but precipitously faster as array size increases; In the thousands or millions, a loop will process 5 to 15 TIMES faster than reduce.

Resource

I've written an article on a criteria set one should consider when deciding over a choice of implementations to use.
tltr: should always choose the fastest implementation when robustness and security are equal....
dev.to/functional_js/squeezing-out...

 

point A is very interesting. i like it. and it makes perfectly sense. and reduces the use cases for reduce :-)

 

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

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.

 

This seems a prime candidate for map though.

const result = original.map(({ a, b }) => ({ a, b }))
 

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
}
 

Another prime example of the JS community getting bored.

 

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.

 

My personal rule of thumb is to use a for-loop if the reduce expression doesn’t fit in one line. That doesn’t mean that reduce functions that take up multiple lines are in general hard to read

 

Goes too far that a linter (just a tool) is trying to force its questionable opinion on the developer ... that tool can't be reconfigured to just take this rule out?

 

of course it can. as they mention in the Repo: XO is a Opinionated but configurable ESLint wrapper :-)

 

Well problem solved then :-)

Anyway yes, I do see a potential problem with Array.reduce - it's so generic that it can be misused ... but for a linter to tell me not to use it, that feels a bit patronizing.

but linters also suggest not to use many other practices when programming. And as a Junior I appreciate such as should I be able to justify the use of something blocked by a linter, I will then adjust linter rules to say I know what I'm doing

 

I love reduce and I use it for almost everything!

 

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

 

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