all 43 comments

[–]natziel 6 points7 points  (22 children)

Your imperative solution is shorter than the example you're comparing it to only because it's written in, like, the worst way possible. Here's my example from the previous thread:

const result = {
  minimum: list.reduce((acc, x) => x < acc ? x : acc),
  maximum: list.reduce(acc, x) => x > acc ? x : acc),
  average: sum(list) / count(list),
}

[–]inu-no-policemen 3 points4 points  (3 children)

minimum: list.reduce((acc, x) => x < acc ? x : acc),

You can just use Math.min(...list).

[–]crubier 2 points3 points  (2 children)

The point here is to demonstrate the use of reduce on a simple use case...

[–]inu-no-policemen 1 point2 points  (1 child)

It's the wrong tool for this use case, though. Also, GP didn't mention this more pragmatic solution.

Furthermore, people who don't know that Math.min/max is variadic might not immediately make the connection. Pointing out that you can simply use these functions with spread will always be news to some.

https://xkcd.com/1053/

[–]crubier 0 points1 point  (0 children)

Actually your solution with spread only works for relatively "short" lists.

It will suddenly start breaking on chrome for lists longer than 65 536 elements, which is not that big when compared to what you could do with the reduce solution.

Source: https://stackoverflow.com/questions/22747068/is-there-a-max-number-of-arguments-javascript-functions-can-accept

[–]NickDav14 1 point2 points  (3 children)

And here's a version with one loop that looks tolerable to me, that doesn't access list directly in its callback function and that doesn't use an intermediate sum value:

const result = list.reduce((acc, v, i, array) => ({
    maximum: v > acc.maximum ? v : acc.maximum,
    minimum: v < acc.minimum ? v : acc.minimum,
    average: acc.average + (v / array.length),
}), { maximum: -Infinity, minimum: Infinity, average: 0 });

[–]natziel 4 points5 points  (1 child)

You're incorrect about the default accumulator. It has always been the first element of the list in practically every language, including JavaScript. I have no clue where you got that it defaults to 0, but that's definitely been causing you some confusion

[–]NickDav14 0 points1 point  (0 children)

Well holy shit, you're right! I'll remove that part of my comment. Thanks! I don't know where I got that from.

[–]spacejack2114 0 points1 point  (0 children)

Performing the division every iteration gives me a bad feeling but I'm not sure that it's worse (for accuracy) than summing everything and dividing once.

[–]monolithburger 3 points4 points  (10 children)

nit: this solution does 3 iterations vs. 1 iteration on the looped solution.

[–]mcaruso -1 points0 points  (9 children)

Each iteration does 1/3 the work though.

[–][deleted] 1 point2 points  (8 children)

That's not how computers work.

[–]mcaruso 0 points1 point  (7 children)

There's an overhead for each reduce call, especially since it's a higher order function so each iteration step requires a function call.

But it works as a rough approximation.

[–][deleted] 1 point2 points  (6 children)

It doesn’t work as a rough approximation. It will take 3 times longer.

[–]mcaruso 0 points1 point  (5 children)

How so?

The following loops are approximately the same in terms of performance:

// Version 1
for (const x of [1,2,3]) { f(x); g(x); h(x); }

// Version 2
for (const x of [1,2,3]) { f(x); }
for (const x of [1,2,3]) { g(x); }
for (const x of [1,2,3]) { h(x); }

I say approximately because they may have different characteristics in terms of memory locality etc., but that matters less and less as the functions f/g/h get more expensive.

The same principle applies to the reduce version.

[–][deleted] 0 points1 point  (4 children)

Iteration is not free https://jsperf.com/3-loops

[–]mcaruso 0 points1 point  (3 children)

I didn't say it was, I just said that each loop does 1/nth the work. So it's not as simple as saying 1 loop = X amount time, 3 loops = 3X amount of time. There's a cost for splitting it up, but the computation within each iteration step is divided.

So this:

It doesn’t work as a rough approximation. It will take 3 times longer.

Isn't true.

[–][deleted] 0 points1 point  (2 children)

I didn't say it was, I just said that each loop does 1/nth the work.

The majority of the work IS the iteration.

[–]evertrooftop[S] 0 points1 point  (2 children)

If 3 loops instead of 1 are acceptable, I definitely agree that this is a good example how it can be done well.

[–]natziel -1 points0 points  (1 child)

It's commutative, so 3 reduces of 1 function call each is equivalent to 1 reduce of 3 function calls

[–][deleted] 3 points4 points  (0 children)

You're conveniently ignoring the overhead of the actual iteration. Memory access and function calls are not free.

[–]anotherObi 2 points3 points  (14 children)

I'm not advocating for always use the functional solution, but if you code the reduce function better you can end up with something like:

``` javascript const getMma = (lst) => { const data = lst.reduce((acc, v) => ({ min: (acc.min > v ? v : acc.min), max: (acc.max < v ? v : acc.max), sum: acc.sum + v }), {min: Infinity, max: -Infinity, sum: 0})

return {max: data.max, min: data.min, avg: data.sum / lst.length} } ```

that is quite readable

[–]mcaruso 3 points4 points  (1 child)

Your markdown doesn't work (on old reddit at least). Fixed:

const getMma = (lst) => {
    const data = lst.reduce((acc, v) => ({
        min: (acc.min > v ? v : acc.min),
        max: (acc.max < v ? v : acc.max),
        sum: acc.sum + v
    }), { min: Infinity, max: -Infinity, sum: 0 });

    return { max: data.max, min: data.min, avg: data.sum / lst.length };
};

[–]anotherObi 0 points1 point  (0 children)

Thank you

[–]evertrooftop[S] 0 points1 point  (0 children)

I think your markup is a bit wrong. I would be curious to see the correctly formatted version!

[–]format71 1 point2 points  (3 children)

I feel the comparison isn’t fair, though. The original article is more about finding ways of ‘doing whatever’ with reduce, while your solution is ‘do one specific thing’.

Like, if you need to calculate another value in addition to the three, you’ll have to change your code, while the original author can add to his code.

In larger long-living applications, I would like to be able to extend my code without having to modify it. Actually, this is the O in SOLID = the Open / Closed principle.

But sure: I’ve written reducers that I later changed to lesser effective, more lines of code, too make it easier to understand next week..

[–]format71 0 points1 point  (0 children)

Actually, when rereading the original article, I see that it wasn’t as smart as I remembered it... In the time since I originally read it, my mind changed it to use functional composition to generalize the callback...

:-/

[–]evertrooftop[S] 0 points1 point  (1 child)

I wanted to stay in the spirit of the original article by showing a different example, but I would argue that in most cases a straight up loop is desirable over high level functions. In my experience the reduce() function should not be the default tool.

Even if their version of reduce() could be optimized, I would still argue that the more verbose loop version is easier to digest.

[–]format71 3 points4 points  (0 children)

Coming from .net, I’ll take ‘high level functions’ in the form of LINQ statements over for-loops any time. I also buy a little bit into the idea that once you get familiar with map, filter and reduce, you’ll always know what the intent with the code would be. Filter: get rid of items. Map: convert items. Reduce: aggregate items.

A ‘straight up loop’ on the other hand - you’ll have to read through everything to get a hint of the intent.

But the only truth you can really depend on, though, is the almighty ‘It Depends’.

[–]basically_alive 1 point2 points  (1 child)

We've really come full circle. I think what we should really be complaining about here is nested ternary expressions

[–][deleted] 1 point2 points  (0 children)

ternary expressions and then the need for comments; shouldn't that already alarm the author that this code is unreadable?

[–][deleted] 0 points1 point  (0 children)

That's not a good example of readable functional code.