
How to use array reduce for more than just numbers - jaden
https://jrsinclair.com/articles/2019/functional-js-do-more-with-reduce/
======
minitech

      function splitLineReducer(acc, line) {
          return acc.concat(line.split(/,/g));
      }
      const investigators = fileLines.reduce(splitLineReducer, []);
    

That’s unnecessarily quadratic. So yes, definitely use the more readable
`flatMap` (with appropriate polyfill):

    
    
      const investigators = fileLines.flatMap(line => line.split(','));
    

(The `flatMap` in the article is not an appropriate polyfill)

The object spread example has the same problem, like c-smile mentioned. Use a
Map for lookups instead.

> Some readers might point out that we could get a performance gain by
> mutating the accumulator. That is, we could change the object, instead of
> using the spread operator to create a new object every time. I code it this
> way because I want to keep in the habit of avoiding mutation. If it proved
> to be an actual bottleneck in production code, then I would change it. ↩

Avoiding (very local) mutation because it’s an FP bad word in a language like
JavaScript is just a terrible way to go about things. Also, this turns out to
be an actual bottleneck way more often than people notice.

~~~
nirvdrum
> Also, this turns out to be an actual bottleneck way more often than people
> notice.

I wish more devs -- particularly web front-end devs -- tried out their work on
something that isn't a top-of-the line developer computer. There are plenty of
devices out there that are computationally constrained and/or memory-bounded.
Moreover, no one ever runs these things in isolation. So, while profiling in
isolation makes a ton of sense to rule out external factors, in the real world
you're competing with all the other open tabs and windows. That's a tricky
problem to model, of course, but it doesn't mean we shouldn't try.

~~~
tomxor
> I wish more devs -- particularly web front-end devs -- tried out their work
> on something that isn't a top-of-the line developer computer.

I agree, and I am, and I do.

I held out on laptop from 2009 until recently, it had a core 2 duo, It was
(and is) a really good machine for showing up sloppy performance hungry
websites, and has been the source of a few of my rants here on HN when
discovering some stupid side JS animation saturating one of it's cores
unnecessarily.

I agree there's a lot of shitty JS code out there. But there are a few of us
web devs that care, and will do anything we can to prevent the crap we see
around us.

~~~
dcbadacd
Slow machines aren't good to just easily notice bad websites, they're also
good to notice bad server-side code and native apps.

------
kace91
At my current project we try to do everything as functional as possible (we're
backend developers using node).

After a year or so of trial and error, we have pretty much stopped using
reduce. The reason is that it pretty much always causes the code to be far
less readable, specially for new devs that come from more object oriented
approaches.

With the .Map, .foreach and .filter, methods we don't usually have that
problem, people get the hang of them quite quickly and find them more elegant,
but reduce always seem to require to stop for a few seconds to learn what it's
doing.

~~~
bcherny
I think it’s reasonable to expect engineers to be able to learn how .reduce
works. It’s part of JavaScript, after all.

If a new hire doesn’t know (a junior dev, or someone without experience in
JS/Scala/etc.), an hour of reading plus coding exercises should be plenty to
get them up to speed (if it’s not, that would be a red flag to me, and could
be a sign of poor performance to come in other areas, too).

~~~
acdha
The distinction isn't “can possibly learn” but “can clearly and immediately
understand while doing the real work”. It's one thing to sit down and play
around with alternate styles, code golfing, etc. but for production work it's
usually best to pick the most understandable way to reduce the cognitive
overhead so when someone is making a change, debugging, profiling, etc.
they're not instead spending time decoding the clever trick their predecessor
(possibly them six months ago) used.

This is particularly true with religiously following functional style in
languages which weren't designed from the ground up to work that way. The
results usually take more time to understand and are often slower because the
runtime is not as optimized for uncommon styles.

------
c-smile
This sample

    
    
        function keyByUsernameReducer(acc, person) {
          return {...acc, [person.username]: person};
        }
        const peopleObj = peopleArr.reduce(keyByUsernameReducer,   {});
    

has O(n*n) complexity and memory consumption of the same magnitude.

This, while being not so functional pure, is better I think:

    
    
        function keyByUsernameReducer(acc, person) {
          acc[person.username] = person;
          return acc;
        }
    

But still... needless function call on each iteration.

~~~
Rockslide
I too prefer to use the "inefficient" notation because it just feels wrong
(kind of an antipattern in the functional sense if you will) to mutate the
argument. And as long as you are not dealing with a huge input list, this
doesn't really matter at all.

Also, why would the memory complexity be n * n? Sure, a new object is created
in each iteration, but its not like you have to keep the previous ones in
memory - all but the last one can be garbage collected.

~~~
megous
There's nothing wrong with mutating an argument. The whole reason JS passes
objects by reference is so that you can do it.

~~~
bendiksolheim
There are tons of wrongs with mutating arguments. The fact that it is possible
to mutate arguments in JavaScript does not make it a good idea. Such functions
cannot be trusted (how can you know what happens when you call it?), and they
are hard to test.

There is _always_ a better alternative to mutating arguments in a function.

~~~
nitely
Sure, but here it's a local mutation, it won't leak to the rest of the
program. There is nothing wrong with mutating acc in reduce.

~~~
tekkk
I agree, should you always test the reducing function separately outside the
reduce? And if so, what's the real benefit of not mutating accumulator? Seems
silly to just copy a transient value which is immediately discarded and then
do it for all reduced elements. Does the original reference to the object even
stay during the iterations? Hmm, I guess it does.

To me it seems weird to be so puristic about a simple reduce-function, which
by-design leads you to mutate the accumulator. I mean for object-accumulators
it definitely is just a massive waste to _not_ to just mutate the argument
directly without copying. Although as a disclaimer I have to say I am big fan
of simple code, were it FP or not. So if you have a messy reduce-function I
guess having it immutable makes it a somewhat easier to manage.

------
sonnyblarney
I see a lot of for loops.

Working on a project in which there are really nary such resources available,
and I'm stuck with only 'for' ... guess what? I don't miss much of these
functions; I just don't find myself reaching for them, wishing I had them.

Either a for loop, or a couple of lines of code, or _gasp_ you end up writing
a short custom utility function to do specifically what you want, which is
generally better optimized anyhow.

~~~
adminu
Usually it is the other way round: you miss what you had somewhere else and
learned to love. When you go back it hurts. Try a 800x600 monitor nowadays and
you will cringe. For me after using functional constructs a lot I find myself
wishing for them in languages where they are not available that easily.

~~~
sonnyblarney
Yes, we miss nice screens when we go back to crappy ones, agreed, but this is
my point: I don't miss most of these lodashy functions when they're not there.
Maybe a little bit.

------
spoiler
I'm not opposed to using `reduce` where it solves a problem in a readable or
performant way, but it seems there's a trend among JS developers recently (I
noticed this in the react ecosystem) to use `map` / `reduce` for _everything_.

Sure, _some_ problems can be solved elegantly and efficiently with it, but a
lost of the time it just impedes readability, and maintainability of the code.
A `for of` loop (or your C-styled for loop) would sometimes be better suited.

~~~
PopeDotNinja
Wouldn't map and reduce be more readable if you were used to reading it? I've
been using a lot of Elixir and Ruby, both of which use map and reduce, and
neither of which make extensive use of for loops. Map and reduce are what I am
used to reading.

~~~
spoiler
When you have some imperative code inside the reduce function and you have to
go out of your way to achieve something (ie, immutability by using
Object.assign, spread, or Array.prototype.concat, etc) and on top of that have
some conditional and imperative code, then the reduce function stops being
elegant, and looks horrible.

Sure, every problem can be solved in a functional way, but JavaScript is not a
purely functional language. Why limit yourself to a subset of the language?
IMO, you should use the _best_ tool you can for the problem. Sometimes that's
reduce, sometimes it's not.

Also, I love Ruby (and used Elixir), and it's not really a comparison that
makes sense, they're different languages with different APIs, syntax, and
there's a category of problems in JS you never have to think about when using
Erlang/Elixir, or Ruby. I could say that I'm used to seeing maps and folds in
Haskell code; doesn't really mean anything in this discussion.

------
rdtsc
Reduce is also known as "fold" in other languages.

[https://en.wikipedia.org/wiki/Fold_(higher-
order_function)](https://en.wikipedia.org/wiki/Fold_\(higher-order_function\))

And especially when using immutable data it can be used for iterating,
traversal (lists and trees, like say an in-order fold), filtering and other
things. But there it is not an just an nice trick like for JS, but it is the
canonical way of doing things.

~~~
saagarjha
Reduce is slightly different from fold because it does not use an initial
value, which means its semantics differ somewhat with regards to restrictions
on types.

~~~
hnra
Reduce in Javascript has the initial value as an optional parameter.

~~~
saagarjha
And it doesn't do types, so that's not really an issue either…

------
ggregoire
To balance all the negative comments about readability, I do think .reduce is
often more readable than its traditional alternatives.

Simply because its use cases are always the same so you can guess the code
without reading it:

* Reducing an array of numbers, with a number as initial value? => We are going to do some operations on those numbers.

* Reducing an array of objects, with a number as initial value? => We are going to do some operations on one or several props of those objects.

* Reducing an array of objects, with an empty object as initial value? => We are going to create a key/value object with computed key names.

Then just by reading the name of the variable on the left of the assignment,
you know exactly what's going on.

And if you don't need to know about the details, you can just jump to the next
statement.

    
    
        const products = [product1, product2, product3]
        const sumPrices = products.reduce(/* I don't need to read this */, 0)
    
        const users = [users1, users2, users3]
        const usersByFirstname = users.reduce(/* I don't need to read this */, {})
        const usersByLastname = users.reduce(/* I don't need to read this */, {})

------
nkrisc
Reader mode is a huge UX upgrade here. Recommend it.

~~~
_xgw
It doesn't show the code's comments though.

~~~
NikkiA
pocket's reader mode does though. (and the rest of the text looks pretty much
identical to firefox's reader mode)

------
jpochtar
reduce() is how you get imperative programming in a functional setting. JS is
a perfectly good imperative language; for all these examples, you may as well
use a for loop.

Personally I prefer a functional style, but if you're going to use an
imperative style, just use JS's built-in imperative for loop and assignment
operators.

------
imtringued
I seriously hope nobody takes the advice in this article seriously.

Here are the conclusions you should take away from the article: Don't use
reduce to reimplement map. Don't use reduce to reimplement flatmap. Don't use
reduce to reimplement groupBy. Don't use reduce to reimplement filter. The
minMaxReducer is the only legitimate use of reduce in the entire article.

The above functions require less boilerplate code. With reduce you will have
to write additional bookkeeping code for the accumulator and without mutable
data structures [!] it would be incredibly inefficient.

[!] We are far away from functional programming at this point.

------
codr7
While we're on the subject of reduction, I really think transducers [0]
deserve a mention.

The general idea is to stack operations and use reduce to perform the entire
transformation in one go, as opposed to chaining calls to map/filter etc.

Which means first class transformation pipelines and less wasted effort among
other things.

[0]
[https://clojure.org/reference/transducers](https://clojure.org/reference/transducers)

------
galaxyLogic
When EcmaSCript 2019 hits the shelves it will have flatMap(). flatMap is the
"corner-stone of monads" (poetically speaking) at which point monads will lose
much of their mystique and things will be much advanced in the programmer
land. You can use monads where you need them.

[http://2ality.com/2018/02/ecmascript-2019.html](http://2ality.com/2018/02/ecmascript-2019.html)

More on ES2019 at [https://medium.com/@selvaganesh93/javascript-whats-new-in-
ec...](https://medium.com/@selvaganesh93/javascript-whats-new-in-
ecmascript-2019-es2019-es10-35210c6e7f4b)

------
Waterluvian
It's funny to see other perspectives. I've never used array reduce for
numbers.

------
yankjenets
Optimize for readability above everything else. Almost all of these examples
I'd argue would be better suited for something like .map(). Little point in
being overly cute, and if you are working with arrays large enough such that
doing multiple passes is that much of a performance hit client-side, you have
other issues going on.

~~~
azangru
> Almost all of these examples I'd argue would be better suited for something
> like .map()

Roughly half of his examples take an array and return either a differently-
sized array, or a different data structure altogether. I can’t imagine how one
could argue for a map in these cases.

~~~
hn_throwaway_99
Use the right tool for the job. Array to potentially different sized array?
Use flatMap (which he implements in his example, albeit not it an optimal
way). Array to single object or value? Use reduce.

------
agumonkey
<deleted useless comment>

~~~
dang
Please don't do this here.

