Coding is as much a matter of personal growth as it is of logic and control-flow. I keep patience, curiosity, & exuberance in the same toolbox as vim and git.
*Opinions posted are my own*
I have to say I disagree on your basic premise, though.
The whole purpose of methods such as map, filter and reduce is preventing the mutation of what gets passed into them.
This is a misconception, in my opinion. Although immutability does indeed contribute highly to readability and reduces bugs, the main benefit of this kind of list processing is in how it communicates intent.
With for loops, every example is different. Each loop must be read, parsed, and mentally executed in order for the reviewer or maintainer to understand what it's for (pun!) The only exceptions to this rule are for loops that closely mimic the behavior of map, by deferring their bodies to some function, but even in those cases your stuck with the semantically useless i variable in the body.
The only part of line 2 with any semantic meaning is things
By contrast; map, filter, and reduce always mean the same things. They are a shared interface representing classes of procedures. forEach communicates the intent to do side effects, map to mutate, reduce to produce a single value from multiple values.
constbetterThings=things.map(betterThing)
To my eyes, this is vastly superior at communicating intent. The amount of verbiage in the first example... Who has time for that?
And remember that the first snippet is the best case of for loop. In most cases, you'll have to read several lines of imperative statements in the body in order to make heads or tails of what's going on.
That being said, just typing .map( won't magically solve all of your readability problems. Naming your functions semantically is key, avoiding lambdas in almost all cases helps.
Ultimately, one needs to adopt the mindset and goal of communicating their intent from the most to least abstract steps, starting at their program's entry point (usually at the bottom of the file) and working their way down.
Now that doesn't mean we should dogmatically forbid all loops. You're right when you say that loops can improve performance in cases. But let's not sacrifice semantics, maintenance, and readability for performance gains which may not be necessary or helpful.
Write semantic, well-abstracted code first. Later, identify and ameliorate performance bottlenecks on a priority basis.
This is a great response. You make valid points about these functions communicating intent. I don't think there is anything wrong with these functions because I use them quite a lot myself. It's only when I am dealing with large arrays of data (which is not all too often) that I will choose a for loop.
I agree a for loop where the index is defined in the loop and incremented looks horrendous. I use for..of in combination with Array.entries() these days if I need a loop and the index value.
Your example could be cleaned up considerably using a for..of loop and doing the following:
const betterThings = [];
for (const thing of things) {
const newThing = betterThing(thing)
betterThings.push(newThing)
}
You could even go one step further and not bother with the newThing constant and just do this:
const betterThings = [];
for (const thing of things) {
betterThings.push(betterThing(thing));
}
I am not using Array.entries() here because we don't need the index, we just want the value inside of the array. You can't argue that map does not look cleaner and in this instance, I would also use map as well. If things was comprised of 50,000 things, I might think twice, but a few hundred or even thousand, I would stick with map.
I definitely agree though, as I said at the bottom of the article. Write your code now and optimise it later on if it becomes a problem. Chances are you're only going to incur 100 or so milliseconds using map or any other method, to the point where you or anyone else wouldn't even be able to tell.
Coding is as much a matter of personal growth as it is of logic and control-flow. I keep patience, curiosity, & exuberance in the same toolbox as vim and git.
*Opinions posted are my own*
Thanks for sharing your view.
I have to say I disagree on your basic premise, though.
This is a misconception, in my opinion. Although immutability does indeed contribute highly to readability and reduces bugs, the main benefit of this kind of list processing is in how it communicates intent.
With for loops, every example is different. Each loop must be read, parsed, and mentally executed in order for the reviewer or maintainer to understand what it's for (pun!) The only exceptions to this rule are for loops that closely mimic the behavior of map, by deferring their bodies to some function, but even in those cases your stuck with the semantically useless
i
variable in the body.Consider
The only part of line 2 with any semantic meaning is
things
By contrast; map, filter, and reduce always mean the same things. They are a shared interface representing classes of procedures. forEach communicates the intent to do side effects, map to mutate, reduce to produce a single value from multiple values.
To my eyes, this is vastly superior at communicating intent. The amount of verbiage in the first example... Who has time for that?
And remember that the first snippet is the best case of for loop. In most cases, you'll have to read several lines of imperative statements in the body in order to make heads or tails of what's going on.
That being said, just typing
.map(
won't magically solve all of your readability problems. Naming your functions semantically is key, avoiding lambdas in almost all cases helps.Ultimately, one needs to adopt the mindset and goal of communicating their intent from the most to least abstract steps, starting at their program's entry point (usually at the bottom of the file) and working their way down.
Now that doesn't mean we should dogmatically forbid all loops. You're right when you say that loops can improve performance in cases. But let's not sacrifice semantics, maintenance, and readability for performance gains which may not be necessary or helpful.
Write semantic, well-abstracted code first. Later, identify and ameliorate performance bottlenecks on a priority basis.
Thanks again for kicking off the discussion!
This is a great response. You make valid points about these functions communicating intent. I don't think there is anything wrong with these functions because I use them quite a lot myself. It's only when I am dealing with large arrays of data (which is not all too often) that I will choose a for loop.
I agree a for loop where the index is defined in the loop and incremented looks horrendous. I use
for..of
in combination withArray.entries()
these days if I need a loop and the index value.Your example could be cleaned up considerably using a
for..of
loop and doing the following:You could even go one step further and not bother with the
newThing
constant and just do this:I am not using
Array.entries()
here because we don't need the index, we just want the value inside of the array. You can't argue thatmap
does not look cleaner and in this instance, I would also usemap
as well. Ifthings
was comprised of 50,000 things, I might think twice, but a few hundred or even thousand, I would stick withmap
.I definitely agree though, as I said at the bottom of the article. Write your code now and optimise it later on if it becomes a problem. Chances are you're only going to incur 100 or so milliseconds using
map
or any other method, to the point where you or anyone else wouldn't even be able to tell.So you're saying you have a nuanced and reasonable approach to a twitter-hot-take-bait topic? That's crazy-talk! 😉