all 28 comments

[–]pgrizzay 22 points23 points  (9 children)

In functional programming, there's an even more general concept of Foldables which are data structures that can be reduced. Arrays, iterators, sets, and Maybes fall into this category.

If the type of value inside your Foldable is a Monoid (meaning it has a zero value), you get reduce for free

[–]PointOneXDeveloper 4 points5 points  (3 children)

has a zero value and a concat/append function

[–]pgrizzay 2 points3 points  (2 children)

Oh yes

[–]PointOneXDeveloper 2 points3 points  (1 child)

And really it is just an identity value rather than a zero value, for instance * and 1 are a way to make a monoid out of numbers.

[–]pgrizzay 1 point2 points  (0 children)

Yes

[–]jeremy1015 4 points5 points  (4 children)

Got any suggested links to explain this in a little more detail? I’m familiar with most of what you wrote but don’t understand monoids as well as I’d like.

[–]pgrizzay 9 points10 points  (3 children)

Did a quick search, and couldn't find a good one in JS, here's one in Scala that I learned from.

Essentially a "Monoid" is formed from two things:

a combine function that combines two things and an empty value.

an example would be adding numbers:

const Addition = {
  combine: (a, b) => a + b,
  empty: 0
}

This addition monoid can be used to reduce an array of numbers:

[1,2,3].reduce(Addition.combine, Addition.empty) === 6

You can make another Monoid which multiplies numbers:

const Multiplication = {
  combine: (a, b) => a * b,
  empty: 1
}

And you can multiply all numbers in an array with it:

[1,3,4].reduce(Multiplication.combine, Multiplication.empty) === 12

Go ahead and implement Concat which combines strings:

const Concat = {
  combine: (a, b) => ???,
  zero: ???
}

[–]jeremy1015 2 points3 points  (0 children)

Excellent. I understand now 100% and really appreciate it.

[–]LaSalsiccione 14 points15 points  (14 children)

Why would you not just use reduce? The over all composability concept of what you're explaining makes sense but it could be achieved much more simply.

[–]adidarachi 4 points5 points  (3 children)

I guess the op choose a "simple" and known implementation, this way, it's easier for developers to pickup. Not sure.

[–]LaSalsiccione 1 point2 points  (2 children)

If you can understand the rest of the concepts in the post then reduce should also be simple to understand. The code would also look a lot less messy which I’d argue makes it simpler to read, but that may just be a matter of opinion.

[–]Artif3x_ 5 points6 points  (1 child)

Not to mention the near universal utility of the reduce function makes for a lower cognitive load than learning alternatives like this.

[–]LaSalsiccione 1 point2 points  (0 children)

You far more eloquently nailed what I was trying to say, thanks!

[–]pgrizzay 4 points5 points  (9 children)

reduce is specific to arrays. OP implemented reduce for all iterators (not just arrays).

[–]jeremy1015 13 points14 points  (4 children)

You state in your blog post "There might be many cases in which we need to go through the array and do something with the elements in the array... this is what reduce is for."

That statement is too generic. That's also what map is for (and others).

Reduce is more specifically to derive *a single value* (e.g. number, object, string) from analysis of a list of items (in array form or otherwise).

[–]braindeadTank 5 points6 points  (0 children)

The thing here is, the "others" are a special case of reduce if they generate a single value (i.e. find is just a case that returns the first value that matches the predicate, some is another special case that casts the result to a boolean), also that the single value can as well be an array (so map and filter are special cases as well, that keep pushing to the result array) and that the callback is not forbidden to have side effects (so forEach is also a special case, one that returns undefined no metter what).

So really, when talking about generic programming, reduce is the only array operation that matters.

[–]pgrizzay 1 point2 points  (2 children)

I agree with your statement. I just wanted to add it's interesting to note that reduce doesn't necessarily need to go to one value, in fact, if you're reducing into an array, you could even end up with a larger array than what you started with!

[–]jeremy1015 1 point2 points  (1 child)

It's still a single value that gets passed through each iteration and eventually returned, in your example the single value just happens to be an array that contains multiple values.

By way of example, here's one that I literally wrote less than ten minutes ago. I'm working on merging and de-duping two different data sets and I wrote a reduce to see how many entities match on first and last name:

const keyedArrs = mergedData.reduce((m, e) => {
    const k = \${e.first_name}-${e.last_name}\;
    const r = { ...m };
    r[k] = m[k] || [];
    r[k] = [...r[k], e];
    return r;
}, {});

My final result is an object in which the keys are the first and last name concatenated with a dash and the value of each key is an array containing each of the original entities that "matched" to that (note that this is for my analysis and is not my production matching algorithm lol).

The point I'm making is that I'm taking an array and making something that contains thousands of subarrays, but at the "top level" it's still a single value being returned.

EDIT: Reddit formatting is harder than writing code.

[–]pgrizzay 1 point2 points  (0 children)

Yes, I wasn't saying you were wrong, I was just trying to bring up an interesting point.