DEV Community

Discussion on: What s wrong with Array.reduce ?

Collapse
 
cullophid profile image
Andreas Møller

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.

Collapse
 
wulymammoth profile image
David • Edited

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);
Collapse
 
costinmanda profile image
Costin Manda

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!

Thread Thread
 
wulymammoth profile image
David • Edited

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

Thread Thread
 
costinmanda profile image
Costin Manda

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!

Thread Thread
 
wulymammoth profile image
David

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!

Thread Thread
 
icyjoseph profile image
Joseph

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

Thread Thread
 
tech6hutch profile image
Hutch

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

Thread Thread
 
wulymammoth profile image
David

All good catches

Collapse
 
pengeszikra profile image
Peter Vivo
let max = Math.max(...[5, 10, 7, -1, 2, -8, -12])
Thread Thread
 
wulymammoth profile image
David

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

Collapse
 
icyjoseph profile image
Joseph

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)
Thread Thread
 
wulymammoth profile image
David

☝️

Collapse
 
icyjoseph profile image
Joseph

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.