JS/TS arrays - Getting a handle on the map function

In a previous post I explained the use of the filter function, it is worth a read for anyone that is reading this and does not feel comfortable with functional programming:  here we go.

When we think of the word, map, at least some of us will instinctively think of a map of the world, or a map of a place (the rest probably of us probably jump to the Map data type found in most languages).

This sort of map helps us navigate from a source location to a destination and this is a helpful way to think of the function map.

We start off with a value (our source) and then we declare a transformation (the destination), apologies if this sounds condescending, I don't mean to be, but I often find it easier to think of concepts in these sort of ways, especially if I am unfamiliar with a term or way of thinking.

I don't use the word transformation, I do this very consciously because 'transformation' hints at mutation, which is not what the map function does at all.

It is accurate to state that the map function iterates through each element in the array it is called on, it then applies the function passed into the map function to each array; finally, a new array is returned.

At this point, I feel it is suitable to show an elementary example:

 1| const numbersArray = Array.apply(null, {length: 11}).map(Number.call, Number)
 2| const result: number[] = numbersArray.map(val => val * val)
 3| console.log(result); // [ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100 ]

I found it funny that I used map to create an array with all numbers from 0 - 10, let's treat line 1 for what it is; a concise way to  generate some data to be used for the example.
If the use of map confuses you on this line then dont worry just gloss over it but if you understand fully, line 1 then you should likely skip ahead.

Line 2 is where I showcase map, it is called from the numbersArray, map takes in a function, in this case, I have passed in val => val * val.

val is the alias we use for the element in the array, of course, val will always be a number as we can see from the typings of the array the return is assigned to, so this function is squaring what is input into the function.

At this point it is important to iterate: the map function does not mutate the array, that is to say, it does not change the values in the array it is called on. Instead it returns a new array, if you want to use that array you will need to assign as has been done in the example.

This is about as much as we can learn from this example, let's clean it up before we continue as we can make this cleaner and help with testing:

 1| const numbersArray = Array.apply(null, {length: 11}).map(Number.call, Number)
 2| const valueSquared = (val) => val * val;
 3| const result: number[] = numbersArray.map(valueSquared)
 4| console.log(result); // [ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100 ]

The change is that we have pulled the function out of map and placed it on line 2, this, for me, makes line 3 much easier to read, it also makes it easy to test and allows us to reuse line 2 as a pure function throughout the function, class or codebase, depending on your requirement.

Here is another example, this time we chain map functions:

 1| const numbersArray = Array.apply(null, {length: 11}).map(Number.call, Number)
 2|       const valueSquared = (val) => val * val;
 3|       const double = (val) => val * 2;
 4|       const result: number[] = numbersArray.map(valueSquared).map(double);
 5|       console.log(result); // [ 0, 2, 8, 18, 32, 50, 72, 98, 128, 162, 200 ]

Line 4 we see a new pure function, this function doubles whatever is passed to it, the output is shown on line 5, we can see that, first, each element is squared and then, secondly, the element is doubled.

Perhaps the question of how this works is popping into your head, well, you should be aware that we have two iterations happening here, the first iteration applies the square function to each element, the second applied the doubling function to each element, so, whilst it is possible to chain these functions you should be aware that this causes additional iterations.

We can solve this by using only one map function, but what if we want to keep those functions separate?

No problem, functional composition has our back:

1| const squareDouble = (val) => double(valueSquared(val));

To understand this, find the value that is passed in, start from the inside and work your way out to figure out the order in which the functions are called, as you can see valueSquared is called first, then double.

This gives us the final example:

1| const numbersArray = Array.apply(null, {length: 11}).map(Number.call, Number)
2| const valueSquared = (val) => val * val;
3| const double = (val) => val * 2;
4| const squareDouble = (val) => double(valueSquared(val));
5| const result: number[] = numbersArray.map(squareDouble);
6| console.log(result); // [ 0, 2, 8, 18, 32, 50, 72, 98, 128, 162, 200 ]

Of course, it may not always be possible to reach a solution such as this, if you are also using filter you may want to map first, then filter, then map again, in this case, you will have to chain, use good judgement on when to chain and when to compose functions.

Comments

Popular posts from this blog

Angular material table with angular 7 - Defining columns using ngFor

Creating contextual drop downs using RxJs, Angular 7 and Angular Material

From nothing to an Angular 7 app running in 5 minutes with AWS