map in JavaScript. A short tutorial for beginners

When I first started learning JavaScript I thought it was hard to understand map. I will attempt to explain map to someone who never heard of it, a beginner trying to learn JavaScript.

map stems from mathematics. In programming, map first appeared in Lisp. We will use Chicken Scheme, a Lisp dialect, for some examples although our main focus is on JavaScript.

map is a symbol for a rule associating values from a list to another list.

In Scheme we would write,

(map (lambda (x) x) '(1 2 3))
=> (1 2 3)

What transformation does map perform? For every x in a set of xs, we make use of a provided procedure that returns an x as a member of a new set of xs. The shape of the set is the same, but the content may differ.

In JavaScript, we would write and thus by use of an identity function map the values like so:

[1, 2, 3].map((x) => x);

map has a few possible parameters. We will look a the two most common, the first two.

If no x is returned from the mapping procedure, undefined will be returned as the value.

Say we have a list in Scheme, a set of numbers. We want to increase each number by one.

(define add-one (lambda (x) (+ x 1)))

(map add-one '(1 2 3))

This solution is Scheme can also be written straightforwardly with a lambda.

(map (lambda (x) (+ x 1)) '(1 2 3))

I think Scheme is a very clear and elegant language. We have a symbol, map. map is a procedure that receives some methods for mapping - usually a transformation - a set of values to another set.

In the first example we have associated an expression with the symbol add-one, a mapping procedure which the procedure will apply to every value of the provided list (1 2 3) treated as data rather than an expression (designated by the ', syntactic sugar for quote).

In JavaScript, we have the very same option. We can either use an existing symbol, an already defined function or write one on the fly (an arrow function).

const add1 = (x) => x + 1;
[1, 2, 3].map((x) => add1(x));

or,

[1, 2, 3].map((x) => x + 1);

However, if we examine the structure of the first example we realize it's possible to simplify the expression.

We take an x and return the x as an argument of add1. add1 returns the argument plus one. This is the same thing as (x) => x + 1.

This is called referential transparency: we can always substitute add1 and (x) => x + 1. We can thus rewrite the code and simplify it:

const add1 = (x) => x + 1;
[1, 2, 3].map(add1);

Because JavaScript has this feature, our JavaScript solution is just as elegant as the Scheme expression.

map does nothing we can't do with a for-loop. However, if we include the intent of map we are to produce a list from another list without mutating the first list (which could not happen in Scheme because data is immutable). Although this is possible with a for-loop we might experience the code as obtuse. Or just clearer. But to some extent this is a matter of taste.

const add1 = (x) => x + 1;
const nums = [1, 2, 3];
const newNums = [];
for(const n of nums) {
  newNums.push(add1(n));
}

Another example:

const nums = [5,4,3,2,1,0];
const isEven = (x) => x % 2 === 0;
const numsEvenOrNot = nums.map(isEven);
// [false, true, false, true, false, true]

But say we do something based on the the index of the element in the list - not the value.

const evalIfIndexIsEven = (x, i) => isEven(i);
const indexEvenOrNot = nums.map(evalIfIndexIsEven));
// [true, false, true, false, true, false]

Say we have two JavaScript arrays,

const lowerCaseLetters = ['a', 'b', 'c', 'd', 'e'];
const upperCaseLetters = ['A', 'B', 'C', 'D', 'E'];

And we want to make a new array in which each element have an appearance as such: '1. A/a', '2. B/b' (…).

We can use the second argument of map to do this.

const letters = upperCaseLetters.map(
  (letter, index) => `${index + 1}. ${letter}/${lowerCaseLetters[index]}`
);
// ["1. A/a", "2. B/b", "3. C/c", "4. D/d", "5. E/e"]

Currently, some JavaScript developers would react with suspicion, when faced with something else than map such as the for-loop. Since this article is aimed at developers even more beginners than me, I think it is important to ask who says map needs to be everywhere?

I have a lot to learn about programming, but a trend I don't want to be a part of the dogmatism that sometimes and under some circumstances exist in JavaScript culture.

To be clear: I consider myself a part of this culture, this is also self-criticism. We who use JavaScript - just as members of other communities - should always strive to decrease our narrow-mindedness. Learning new languages is one way.