var r = array.map(x => x*x).reduce((total, num) => total + num, 0);
vs its for-loop counterpart: var r = 0.0;
for (var j = 0; j < array.length;j++){
var x = array[j];
r += x*x;
}
Maybe I'm just not as smart as everyone else, but I only have to keep like two things in my mind when looking at the for loop (easy), whereas in the map-reduce case, I need to have a very "holistic" view of what's going on (hard).[1] https://stackoverflow.com/questions/17546450/for-array-is-it...
It should be written like this, IMO:
var r = array
.map(square)
.reduce(sum, 0);
Differences:1. Separating out actions by line
2. Pass named functions that describe what they do
var r = array.reduce((total, num) => total + (num * num), 0);
"Simple" is great until you're debugging off by one errors or any other errors between chair and keyboard. Complexity, in the sense that this guy rails against, is expressiveness. And, expressive code beats "simple" code every day of the week.
--Quick edit Imagine that loop in assembly. It's objectively "less complex" yet you didn't express it that way. Why? I think the answer to that question is the heart of the positive argument for programming abstractions (and the "complexity" they bring).
Except that it's not. Read the SO answer I linked.
> The nooks for bugs in your "simple" loop are so profound that, in a language with expressive maps, I wouldn't accept that code in a review.
Yep, I've definitely worked with people like you. With all due respect, I just think you're fundamentally wrong. Off-by-1 errors will happen no matter what, and even a rudimentary test suite can catch those.
I was referring to mental overhead; though in a more modern language like rust the performance implications would be far less profound than the linked SO discussion.
I don't think off-by-1 errors are that big of a foot gun, compared to, like, having a null as part of the language, or pointers, or about a million other things.
> I was referring to mental overhead
Gotcha. I still think that "linear step-by-step" thinking like "you take stuff out of a bucket, and then do stuff to it" is easier to parse than map-reduce, which requires you to have a "big picture" view of the whole process.
No? Not only can they happen, but I'd argue that it's easier for them to occur when using `reduce`, because you have to remember the initializer. Omitting it has no effect in the given example, but the given example is contrived. It's not rare to be dealing with something more complex than a list of numbers, so let's try that instead.
Let's say your items are a list of nodes. You want to sum their payloads to determine their aggregate cost. And, like kgwxd[1], you see `map` as superfluous. You've got this. You write:
nodes.reduce((sum, x) => sum + x.value)
Uh-oh. Now the fact that there is no initializer does affect the result.I would say that's not an off by one error. Its a similar scale and type of error, but not the same.
let square = (n) => n*n
let sum = (s,n) => s+n
let r = array.map (square).reduce (sum,0)I tend to think there's some additional overhead from Javascript's particular object oriented implementation of mapping and reducing when compared with haskell's i.e.
f xs = foldr + 0 (map (\x -> x * x) xs)
seems a bit easier to me.Is it habit? Probably. But I really find it hard to understand why maps and folds are simpler than manual iterative loops.
f = foldr (\x total -> total + (x * x)) 0
but this is actually a counter argument. look at all these ways of doing this simple operation with higher order functions. plenty of replies from intellectual napoleons like me, too. whereas with the for loop, there's nothing much to say, which seems like a good thing.
i genuinely don't know how to feel about this.
Yes, obviously they can learn, and I don't think learning a bad thing (It's absolutely a necessary skill for a programmer, or anybody), but if the only place in the code that ever uses map/reduce is that one location, then programmers that come by it are likely not going to get a lot out of the time they're going to sink trying to figure it out. If it was just a basic loop, then overall people will likely spend less time figuring it out. For one piece of code it's not that big of a deal, but if your code-base is just full of fun little one-liners everywhere, it quickly becomes a mess to understand.
But with that, if your code is already full of stuff like map/reduce, then I think using map/reduce instead of the basic loop may very-well be preferred for readability (Though of course, your citation brings up performance concerns, which should also be taken into consideration). I think most important is just keeping the code-base consistent.
With map reduce I see the following:
- We've got an array
- We're going to loop through it and square everything
- We're going to take that and add it all together
- The result is r
When I read the for-loop counterpart:
- We have a variable called r that we're initializing to 0
- We're starting a loop with j as the index initialized to 0 iterating through an array
- We've got a variable x on each iteration that is the current position in the array
- We're adding the square of x to r
Maybe it's a personal quirk. for-loops make me feel stupid.
> - We're going to take that and add it all together
Here, we have an imaginary variable: namely, "that." Something that we need to keep track of and is not explicitly referenced anywhere. It's relatively easy when you only have one of these things, but with complex map-reducer logic, you can end up having a whole bunch of these.
In the for-loop, everything I use is explicitly referenced. There's no "that" -- everything is "r" or "x" or "j."
We have a list of numbers that we want to square and add up, instead of the more mechanical steps with the loop approach.
(loop for x in a summing ( * x x ))
And in fact, many languages have only one or the other as a standard idiom. So it's really pretty simple: Use the one that's a standard idiom in the language that you're using.
Your language offers both? It's still pretty simple: Use the one that's better understood by the people that you're working with.
They understand both? The project still probably leans one way or the other. Prefer the one that is more commonly used in the project. Projects wind up using subsets of languages; using things outside the subset, while valid, looks "odd" within the project.
It's all about minimizing the cognitive load on the reader. It's not about what the writer prefers.
I can write that using various maps, folds, flat_maps, and reduces, but no one else would understand what is going on in the code. Maybe someone will post the right idiomatic functional way to do that, but nothing elegant comes to mind.