Loops make me dizzy. I would rather write my code using the map
function. It’s similar to a foreach
loop, but with a twist. Instead of traversing a collection, map
transforms a collection. I only realized this similarity, however, after writing many loops where I should have used map
.
Today, this is how I decide which to use:
- Map function — use when transforming a collection
- Foreach loop — use when traversing a collection
- For loop — only use when I don’t have a collection
Transforming Collections
Collection is a general term — it’s any variable that contains elements. For example, my collection of selfies that I took at an art museum. I want to transform my selfies into black-and-white photos before uploading them to my social feed.
Foreach Loop
Assume I have a pure function called grayscale
that takes an image and returns a grayscale version of that image.
const selfies = [selfieA, selfieB, selfieC];
const coolSelfies = [];
// ES6 JavaScript foreach loop syntax
for (const selfie of selfies) {
coolSelfies.push(grayscale(selfie));
}
This code works fine but has a readability issue. A future maintainer of the code must spot the push
statement to understand the relation between selfies
and coolSelfies
. This may seem like a small gripe. But imagine if that statement was buried under more lines of code. The relationships between collections can become confusing.
Map() Function
I should have written the loop using the JavaScript array map
function.
const selfies = [selfieA, selfieB, selfieC];
const coolSelfies = selfies.map(selfie => {
return grayscale(selfie);
});
This code highlights the relationship between the two collections — coolSelfies
is declared as a transformed version of selfies
. Transforming a collection means looping through a collection for the purpose of building a new collection. Using map
clarifies this intent to readers.
Code Clarity
In JavaScript, the arrow function syntax allows an implied return
statement if I leave out the curly brackets.
const coolSelfies = selfies.map(selfie => grayscale(selfie));
Depending on how grayscale
is defined, I can make this code even shorter by removing the anonymous arrow function.
const coolSelfies = selfies.map(grayscale);
I can read this code easier than a loop. But not all loops should be avoided. Loops that don’t transform a collection should stay as foreach
loops.
Traversing Collections
Traversing means iterating or looping through a collection. For example, I want to post each of my cool selfies to my social media feed. Assume I have a function upload
that takes an image and uploads it to the internet.
for (const selfie of coolSelfies) {
upload(selfie);
}
An alternative choice would be using the JavaScript array forEach
function.
coolSelfies.forEach(selfie => upload(selfie));
This is a case where a foreach
loop is better than a map
function, because of several reasons:
upload
has no return value, so there is no way to create a new collection from the loop.upload
isn’t a pure function. It’s best practice to not usemap
with functions that have side-effects, such as starting an upload.
Code Clarity
At this point, I must contradict myself. Loops are often more readable than map
. Programmers understand loops. Loops are common. Loops exist in almost every programming language.
The map
function is not as widespread. It involves functional programming. Functional programming is not how most people learn to code, regardless of being trained or self-taught.
I’ve refactored many foreach
loops into map
functions thinking I was making the code more readable. I try not to do that anymore. It’s not worthwhile to tear up vetted code just to avoid loops.
Avoid For Loops
A less drastic approach is to avoid writing for
loops. Here’s my original loop as a for
loop:
const selfies = [selfieA, selfieB, selfieC];
const coolSelfies = [];
for (const index = 0; index < selfies.length - 1; index++) {
coolSelfies.push(grayscale(selfies[index]));
});
The for
loop signature (the length
conditional and the index
variable) takes up a lot of space in my code. But the size of the loop and the direction of the loop aren’t actually that important. A foreach
loop would be better here because it abstracts away those variables.
I still use for
loops, but only for tasks that a foreach
loop couldn’t accomplish. Such as precise array manipulation or looping a constant number of times.
Using the right loop for the job
Any type of loop will get the job done. But I like to use the most specific type of loop that I possibly can. This clarifies the code and prevents bugs.
- For loop — general looping
- Foreach loop — looping through a collection
- Map function — transforming a collection into another collection
If you want to learn more about using map
in your code, read up on functional programming. Functional programming is all about combining map
with other transformative functions, such as filter
and reduce
.
I’ve been out of the loop of language development for so long that neither foreach nor map are familiar concepts. foreach is so obvious and understandable that I got it instantly from seeing one example. Further, its benefits are immediately obvious. If I refactor my application to use a binary tree for a collection instead of an array, I don’t have to rewrite all my loops!
map, on the other hand, will take some brain rewiring. I haven’t done any sort of functional programming since LISP in college and that has faded, and I daresay that I actually got the hang of LISP more than any of my classmates back then.
I’d suggest that the interest of your code being easily understandable to anyone who might need to see it would recommend using foreach until you have an application that significantly benefits from map, where map expesses something fundamentally different than a simple loop, rather than just expressing the same thing in an arguably better way.