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.
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.
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.
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 {% 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.
Latest comments (81)
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:
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 usingreduce
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.
I can talk about the "good use case" a little bit.
Let's start with the classic, adding numbers.
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.
In here we create a new array with the unique elements of each array.
Notice how in the functions
add
andintersection
don't mention anything about anaccumulator
or acurrentValue
? That's the power ofreduce
. I guess you can say that it can "increase the capacity" of a binary operation. This in my opinion is the best use ofreduce
.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 wherereduce
can shine.I talk about this in more detail in this article.
This seems a prime candidate for
map
though.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.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)
Math.max(...nums)
for the winA solution in the spirit of the latest trends. Who does not want to go up requires the rest to go down
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.
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.
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...
Can you give an example of a function that cannot be composed with other functions to make a larger function?
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?
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.
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.
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.
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.
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 love reduce. Screw eslint. As long as you don't use an inline callback method, reduce is awesome.