Functional Array Methods Part 1

This is part 1 of a series about the non-mutating methods of JavaScript arrays. I won't be covering push, pop, unshift or other array methods that modify arrays in-place. I also won't be covering any of the iterator stuff. (Does anyone use those anyway?)

I'll just be covering array methods like map filter and reduce, methods that return a new copy of the array, but with whatever transformation is applied by a given function.

I feel like using these is a good first step toward functional programming and away from procedural or object-oriented styles.

If you want to run the code associated with this series, it's all in this repo: https://github.com/NerdcoreSteve/functional-array-methods

Ok, with all that out of the way, let's get started with a simple one concat.

concat

console.log([1, 2, 3].concat([4, 5, 6]))  
/*
prints  
[1, 2, 3, 4, 5, 6]
*/

An array's concat method takes another array and attaches it to the end of the first.

We can chain as many of these as we like, like so:

console.log(  
    [1, 2, 3]
        .concat([4, 5, 6])
        .concat([7, 8, 9])
        .concat([10, 11, 12]))
/*
prints  
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
*/

So, concat returns an array, which has a concat method, which returns an array, which has a concat method, which returns an array.

[1, 2, 3].concat([4, 5, 6]) gets us [1, 2, 3, 4, 5, 6].

Doing another concat([7, 8, 9]) on the end of that gets us [1, 2, 3, 4, 5, 6, 7, 8, 9].

And the final .concat([10, 11, 12]) on the back of that gets us [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].

Notice I don't have to store these intermediary values into their own variables. I can simply do another .concat after each method call, creating a flow of data from the start to the end.

In JavaScript land, this is called chaining.

Another important thing to note is that concat didn't change the original array in any way. It always returns an entirely new array.

This is a hallmark of functional programming, we don't mutate data, we use functions to return new values, then apply functions to those values (returning new values), and so on, until we get our result.

map

Let's say you had an array of numbers like [1, 2, 3, 4, 5] and you wanted to double each value in that array. The result you want is [2, 4, 6, 8, 10].

Back before I knew how to use map, this is how I'd do that:

const  
    arr = [1, 2, 3, 4, 5],
    doubleArr = arr => {
        var newArr = []
        for(var i = 0; i < arr.length; i++) {
            newArr.push(2 * arr[i])
        }
        return newArr
    }

console.log(doubleArr(arr))  
/*
prints [2, 4, 6, 8, 10]  
*/

Wow, that is a lot to type just to get that result. Here's how you do it with map:

console.log([1, 2, 3, 4, 5].map(x => 2 * x))  
/*
prints [2, 4, 6, 8, 10]  
*/

Doesn't that for-loop above just seem clunky compared with this?

With the map method, we take an array of values, apply a function to each value, and return an array of those results.

In this case we took the values from the array [1, 2, 3, 4, 5], applied the function x => 2 * x to each value, and put the results in an entirely new array, namely [2, 4, 6, 8, 10].

An here's another example of chaining:

console.log(  
    [1, 2, 3, 4, 5]
        .map(x => 2 * x)
        .map(x => x + 2)
        .map(x => x / 4))
/*
prints [1, 1.5, 2, 2.5, 3]  
*/

If we map x => 2 * x over [1, 2, 3, 4, 5] we get [2, 4, 6, 8, 10].

Mapping x => x + 2 over that we get [4, 6, 8, 10, 12].

Mapping x => x / 4 over that we get [1, 1.5, 2, 2.5, 3]

More In-Depth Example

Here's a final example that turns 'pirates of the caribbean' into 'Pirates Of The Caribbean':

console.log(  
    'pirates of the caribbean'
        .split(' ')
        .map(s => s.split(''))
        .map(([first, ...rest]) => 
            [first.toUpperCase()].concat(rest))
        .map(arr => arr.join(''))
        .join(' '))
/*
prints  
Pirates Of The Caribbean  
*/

First we take the string "pirates of the caribbean" and calling the .split method on it, which returns an array of the split strings, namely ['pirates', 'of', 'the', 'caribbean'].

Next, we map over each string and split them by each letter, giving us this array of arrays:

[ [ 'p', 'i', 'r', 'a', 't', 'e', 's' ],
  [ 'o', 'f' ],
  [ 't', 'h', 'e' ],
  [ 'c', 'a', 'r', 'i', 'b', 'b', 'e', 'a', 'n' ] ]

We then map over this array of arrays, with this function:

([first, ...rest]) =>
    [first.toUpperCase()].concat(rest)

The ([first, ...rest]) bit does a bit of destructuring to grab the first element of the array and its tail, capitalize that first element, and then use concat to join it all back together.

More specifically, ['p', 'i', 'r', 'a', 't', 'e', 's'] gets split into 'p' and ['i', 'r', 'a', 't', 'e', 's'].

'p' gets turned into 'P'.

Then we join it all together like so: ['P'].concat(['i', 'r', 'a', 't', 'e', 's'])

giving us ['P', 'i', 'r', 'a', 't', 'e', 's'].

Then we map over the array of arrays again, this time rejoining each array back into a string, giving us ['Pirates', 'Of', 'The', 'Caribbean'].

Finally, we call join on this array, giving us 'Pirates Of The Caribbean', which is the result we wanted.

Stay tuned for part 2, coming soon!

Looking for a software developer?