How to FP

ReusabilityVariablesLoopingPipelines

Home»Bottom-up»Looping

Loops are so last millennium!

If you've done much programming, then you've almost certainly used looping constructs. JavaScript offers numerous ways to loop in an imperative manner (meaning you tell the code how to loop rather than just giving it a list of items and letting it decide how to loop):

  • for: repeat a code block a specified number of times
  • for/in: repeat a code block for each of the properties of an object
  • for/of: repeat a code block for each value in an iterable object, such as an array
  • while: repeat a block of code while a specified condition is true
  • do/while: same as while, but always applies the code block at least once

If you think these are easy to use, then you're fooling yourself. You've just been using them for so long that you've forgotten how confusing they were the first time you saw them. Consider the for loop:

No variables were harmed in the making of this code.
1
2
3
4
5
6
7
8
9
10
11
// JUST DON'T DO THIS!
const nums = [1, 2, 3, 4, 5]
const len = nums.length

let sum = 0 // DON'T DO THIS!

for (let i = 0; i < len; i++) {
  sum += nums[i] // Sum is a mutant!
}

console.log(`The sum is ${sum}`)

Easy, right?

Don't be absurd. There is nothing easy about this. Let's look at it a bit more carefully.

First, what on Earth could this possibly mean?

(let i = 0; i < len; i++)

OK, let i = 0; is obvious enough: we're setting a local, mutable variable to 0. By why i? Unless you've been taught how this works, it's not obvious that i is an index, or that we'll be incrementing it.

So what is this i < len;? A conditional, obviously, and we can see that len is 5 and i is 0, so i < len is obviously true, but so what? What does that do?

And then there is this mystifying i++. Again, you have to be taught (and then memorise) that ++ is the increment operator and that here it is applied to i in the postfix position, which would normally mean that we would use the value first, and then increment it.

But the truth is that in the for loop this increment is not applied until after the loop has run, and then is applied before the next iteration of the loop. So despite that nearly everyone who writes for loops uses i++, it really should be ++iand that will also work (try it).

Wait! Both ++i and i++ both work? How does that make any sense at all?

Eventually, the new programmer figures out that i < len; is used to stop the loop. When this condition evaluates to false, the loop stops running. This condition is checked before every application of the code block, so if I write i > len; the loop never runs at all (try it).

But if you're familiar with this loop, then you'll know that the most common mistake is to write i <= len;, which causes the loop to run an extra iteration (try it). This throws no error, but results in sum being NaN no matter what we pass it. Not good.

So gnarly is the for construct that later versions of JavaScript have introduced for/in and for/of. These simplify our loop, but again, you have to remember when to use in and when to use of, and this still ends up mutating a variable, though at least we don't have to worry about indexes:

The only thing necessary for the triumph of evil is for good men to do nothing.
1
2
3
4
5
6
7
8
9
10
11
// DON'T DO THIS EITHER
const nums = [1, 2, 3, 4, 5]
const len = nums.length

let sum = 0 // Ugh

for (num in nums) {
  sum += num // Sum is still a mutant!
}

console.log(`The sum is ${sum}`) // Oh, look what happened here!

Ha, ha. Yeah. You thought that for/in would pass you each value in the array one at a time? Psych! Seems obvious, but think again. It passes the index. So we're back to indexes! Let's try that again:

The greatest trick the Devil ever pulled was convincing the world he didn’t exist.
1
2
3
4
5
6
7
8
9
10
11
12
// DON'T DO THIS EITHER
const nums = [1, 2, 3, 4, 5]
const len = nums.length

let sum = 0 // Ugh

for (i in nums) {
  // console.log(typeof i) // uncomment this for a surprise
  sum += nums[i] // i is the INDEX -- ick!
}

console.log(`The sum is ${sum}`) // Working, but definitely not intuitive

Wondering why we ended up with 0012345 in the broken version of for/in above? That's because for some difficult-to-grasp reason, the index is passed in as a string, so the += operator concatenates strings instead of adding numbers. WTF? Is this some kind of sick joke?

Here the TC-39 committee tried to make life easier for JavaScript coders and ended up making things just that much less intuitive.

The above constructs work, and you can eventually get reasonably proficient at them, but there is nothing intuitive about them and they are, truly, one of the most difficult things for new programmers to learn.

Surely, there is a better way! Step into the light...

Functions can call themselves! No, really!

One simple way to handle looping through a set of items, such as an array of numbers, is to process each number in turn. We can use a function call to do this, and rather than putting our function in a loop, we can just pass it the list, have it take the head of the list, process it, and then call itself again with the tail of the list, which is just the rest of the list.

Show Evil Terminology

And we can continue this process until we run out of list elements. Then we will stop and return the fully processed list, whatever that looks like.

For example, let's take our sum above. Adding a list of numbers together is the same as adding each number to the number after it and keeping a running total.

If you think about it, that's precisely what our loop above does. It creates a running total in a sum variable and initialises it to 0, then adds the numbers in the list to it sequentially until it exhausts the list. What's left is our final total.

We can do the same thing by creating a function that takes our list and our running total (which defaults to 0), gets first item from the list, adds it to the running total, and then calls itself again on the rest of the list, passing it the current subtotal:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function sum (nums = [], total = 0) {
  console.log(`Total is ${total}`)
  
  // If nums is empty, return the total to stop the loop
  if (nums.length < 1) {
    console.log(`Returning the total of ${total}`)
    
    return total
  }

  // Get the head (first number) and the tail (remaining numbers)
  // The tail must approach the base case above so the loop terminates
  const [head, ...tail] = nums

  console.log(`Adding ${head} to ${total}`)
  
  // The function calls itself with the remaining numbers and the new total
  return sum(tail, total + head) 
}

console.log(`↪ The sum of [] is ${sum([])}`)
console.log(`↪ The sum of [5] is ${sum([5])}`, '')
console.log(`↪ The sum of [5, 7, 9] is ${sum([5, 7, 9])}`)

That works. Of course, we could have written the function more simply this way:

1
2
3
4
5
6
7
8
// Not the best way to go about this, usually
function sum (nums = []) {
  if (nums.length < 1) { return 0 }

  const [head, ...tail] = nums

  return head + sum(tail)
}

So why not do it that way? Well, let's see...

A minor benefit of having the running total as a parameter to the function is that we can set it to something other than zero. For example, we could add a list of numbers to 100 instead of zerosum([1, 4, 9, 16], 100)returning 130 instead of 30.

But a more important reason is found in line #7: return head + sum(tail). By choosing to do it this way, we have to push our first function call onto the stack (in memory) and wait for the second call to return before we can return from the first. Then we have to push the second call onto the stack and wait for the third, etc.

If the list is 1000 items long, then we have to hold 999 calls on the stack waiting for that last call to return. That takes a lot of memory. But we can't do the additionhead + sum(tail)until we know what sum(tail) is, right? So we're stuck.

But if we do it like this:

return sum(tail, total + head)

Then we know what tail is and we know what total + head is at the time of the function call. So instead of pushing our function onto the stack and waiting for the second iteration to return, we can simply replace the first function call with the second, right?

Essentially, writing our function-that-calls-itself in this manner makes it work exactly like a loop. So it's as fast and uses as little memory, effectively. Now we get the benefits of the speed and efficiency of a loop without all the unintuitive looping constructs: the best of both worlds.

Show Evil Terminology

Looked at another way, the second example abovereturn head + sum(tail)runs nested:

1
2
3
4
5
6
7
8
9
10
11
sum([1, 2, 3, 4, 5]) === 1 + (
  2 + (
    3 + (
      4 + (
        5 + (
          0
        )
      )
    )
  )
)

Which means it actually adds the list from last to first: 0 + 5 + 4 + 3 + 2 + 1. This is because it has to unwind the stack, returning from each inner call before it can return from the outer call that wraps it.

The first example abovereturn sum(tail, total + head)just makes successive calls to the same function:

1
2
3
4
5
sum([1, 2, 3, 4, 5]) // replaced by
sum([2, 3, 4, 5], 1) // replaced by
sum([3, 4, 5], 3)    // replaced by
sum([4, 5], 6)       // replaced by
sum([5], 10)         // returns 15

So this version adds in the order you'd expect: 0 + 1 + 2 + 3 + 4 + 5. Each call completes by making the next call, so the calls can replace each other on the stack. There's nothing to unwind. When the last call returns, that's our expected value.

Compare them:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function sum (nums = []) {
  if (nums.length < 1) { return 0 }

  const [head, ...tail] = nums
  
  const total = sum(tail) // no different from our other version, really
  
  console.log(`Adding ${head} to ${total} (non-tail).`)

  return head + total
}

console.log(`sum([1,2,3,4,5]) is ${sum([1,2,3,4,5])}`)

function tailSum (nums = [], total = 0) {
  if (nums.length < 1) {
    return total
  }

  const [head, ...tail] = nums

  console.log(`Adding ${head} to ${total} (tail version).`)
  
  return tailSum(tail, total + head) 
}

console.log(`tailSum([1,2,3,4,5]) is ${tailSum([1,2,3,4,5])}`)

Each of the three lines of our function block has a specific purpose.

For a function calling itself to work, we must observe two rules:

  1. We must have a base case where the function returns something without calling itself again. This stops the recursion.
  2. Each time we call the function from within the function we must call it with different arguments, and the arguments must approach the base case! Otherwise we never get to the base case and the recursion never stops.

Let's look at the lines one at a time:

2
if (nums.length < 1) { return total }

This is our base case. It tells our function when to stop calling itself. When there is nothing left in the array that we're passing to our functioni.e., the argument is the empty listthen we just return our running total. That stops the loop. It's essentially a guard.

4
const [head, ...tail] = nums

We are always going to operate on one element of the array at a time, so we'll need to lop the head off the array. When we next call the function, it will be on the remaining elementsthe tailso we need to hang on to those as well. Don't make the mistake of calling the function recursively on the same arguments (nums).

Recall that arrays are passed by reference, which means they are not copied in but merely pointed to. So we'll want to copy the tail and use the copy rather than mutating the original list. Destructuring and the ... (rest) syntax achieves that for us.

When we do:

const [head, ...tail] = [1, 2, 3, 4, 5]

Then:

  • variable head has value 1
  • variable tail has value [2, 3, 4, 5].

And these are new values in new variables. Our initial array remains unchanged.

Finally, we do the work in our third line of code:

6
return sum(tail, total + head)

First time through, our total defaulted to 0, so given the head and tail above, this becomes:

return sum([2, 3, 4, 5], 0 + 1)

Watch it in action:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function sum (nums = [], total = 0) {
  console.log(`total is ${total}`)

  if (nums.length < 1) {
    console.log(`Returning the total of ${total}`)
    return total
  }

  const [head, ...tail] = nums

  console.log(`head is ${head} and tail is ${JSON.stringify(tail)}`)

  console.log(`Calling sum(${JSON.stringify(tail)}, ${total + head})`, '')

  return sum(tail, total + head)
}

console.log(`The sum of [1, 2, 3, 4, 5] is ${sum([1, 2, 3, 4, 5])}`)

As you can see, the total accumulates the value of our running total. But we could accumulate anything, right?

Consider an example where we have an array of numbers and we want to convert it to an array of the squares of those numbers. In other words, we want to loop through the array squaring each element and returning a new array of the squares. Easy!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function mapSquares (nums = [], accumulator = []) {
  // When nums is empty, return the accumulated squares
  if (nums.length < 1) { return accumulator }

  // Get the head and tail
  const [head, ...tail] = nums
  
  // Append the square of the head and call again with the tail
  return mapSquares(tail, [...accumulator, head * head])
}

console.log(`[] mapped to its squares is ${JSON.stringify(mapSquares([]))}`)
console.log(`[5] mapped to its squares is ${JSON.stringify(mapSquares([5]))}`)
console.log(`[1,2,3] mapped to its squares is ${JSON.stringify(mapSquares([1,2,3]))}`)

Passing an accumulator makes it possible to do almost anything a loop can do by simply writing a function that calls itself.

Writing functions that call themselves is easy and fun, but functional JavaScript is all about reusability. It doesn't take writing too many functions like the ones above to start to see certain patterns emerging.

When we wrote our sum function, we took a list of items and reduced it down to a single item. We can write a function called reduce that takes a list of items and reduces it to a single output. We just need three things:

  1. The code block that will be executed each time the function runs
  2. The starting value of our accumulator (0 and [] respectively above)
  3. The list on which we'll operate ([1, 2, 3, 4, 5] above)

Show Evil Terminology

But we want our reduce function to be generic, so we'll want to pass it the code block. How do we pass code blocks around in functional JavaScript? Functions, of course. So our reduce function will take:

  1. A function to be applied to the accumulator and each item of the list in turn
  2. The starting accumulator (e.g., running total)
  3. The list of values

And we can see that the function we provide as our first argument must:

  • Take as parameters:
    1. The accumulator (running total) as an argument
    2. The head of the list as an argument
  • Return a new accumulator value, which will be used on the next call (e.g., total + head)

For example, if we want to sum up a list of numbers, the function we pass to reduce might look like this:

(accumulator, value) => accumulator + value

The above example takes our running total (the accumulator) and the next value in the list (value) and returns the sum (accumulator + value), which becomes the new running total (accumulator), which is then passed to the next call to the function along with the next value from the list. And so on until we run out of values in the list. Then the final total is returned.

When we map our list to the squares of the numbers, things get even simpler. We know we're just mapping from one array to another of equal size, right? So our starting accumulator is always []. We don't really need to pass it to our function as it never changes. Thus, we can create a map function that:

  • Takes as parameters:
    1. A function to apply to each item in a list
    2. A list of items to apply it to
  • Returns a new list of the results from passing the originals list's items through the function supplied

Modern JavaScript provides functions such as map and reduce (and many more) already implemented for you. You just need to supply the right arguments.

The built-in versions work in a very OO way: You call the map method on the array (list) you want to map, pass it the function you want to apply to each item in turn, and it returns a new array of the results.

Similarly, you call the reduce method on the array you want to reduce in some way, pass it the function (that takes the accumulator and each item in turn and returns a new accumulator value), and pass it the starting accumulator. It returns the final accumulator value, whatever that is.

But there is an even better way to do this functionally by pulling the methods out into their own functions, and passing the lists they operate on as a parameterthe last parameterrather than as the caller of a method.

For this purpose, there are numerous libraries out there, the most popular of which is Lodash. (But only use the new lodash/fp module! The original module is broken.).

The Ramda library is much better, having been built (mostly) correctly from the getgo. The best, however, is Sanctuary, which is scrupulously correct, but this also makes it difficult to use in production. There is no slack at all. You must be meticulous. Ramda is probably a good middle ground.

Show Evil Terminology

For most cases, Ramda is your best bet unless you are already committed to something else. Below we examine the most common functions/methods available and show how they can be used both in vanilla JavaScript and in Ramda.

(Note: While there is a Node framework called VanillaJS with no apparent sense of irony, what we mean here by vanilla JavaScript is JavaScript used without adding libraries or frameworksusing only the built-in functionality.)

(P.S. the Ramda REPL is awesome. You can play with all this stuff in there, and all the Ramda functions are automagically imported. And you can bookmark your examples.)

Use map to modify each value in a list

The map function (or method) does exactly what our mapSquares function did above, except generically: we can supply any function we like and it will be applied to each item in the list in turn, returning a new list of the mapped items.

In other words, to get it to square each of the numbers in our array, we need to pass it a squaring function. x => x * x will work:

Look, ma! No loops!
1
2
3
4
5
6
7
8
9
10
11
12
import { map } from 'ramda'

const nums = [1, 2, 3, 4, 5]

// Vanilla JS: map as a method on array
const vjsSquares = nums.map(x => x * x)

// The purely functional way with Ramda: pass the array
const ramdaSquares = map(x => x * x, nums)

console.log(`The squares by Array.map: ${JSON.stringify(vjsSquares)}`)
console.log(`The squares by Ramda's map: ${JSON.stringify(ramdaSquares)}`)

Both the built-in map method and Ramda's map function work the same, so why use the latter? We'll see advantages when we get to pipelining functions below.

If you're working on a small project and you can do it entirely in vanilla JS without importing a library such as Ramda, then maybe you should just use vanilla JS. But on most larger projects, you're going to need a library such as Ramda for many things, so you might as well use Ramda functions over the built in ones.

But more on that below. Let's take another example of map before we move on:

1
2
3
4
5
6
7
8
9
10
11
12
import { map, toUpper } from 'ramda'

const colours = ['red', 'green', 'blue']

// Vanilla JS
const vjsUppers = colours.map(s => s.toUpperCase())

// The purely functional way with Ramda: pass the array
const ramdaUppers = map(toUpper, colours)

console.log(`The uppercased colours by Array.map: [${vjsUppers}]`)
console.log(`The uppercased colours by Ramda's map: [${ramdaUppers}]`)

OK, one more:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { map, toUpper } from 'ramda'

const colours = ['red', 'green', 'blue']

// Uppercase e's if length > 4
const longWordBigE = s => s.length > 4 ? s.replace(/e/g, 'E') : s

// Vanilla JS
const vjsUppers = colours.map(longWordBigE)

// The purely functional way with Ramda: pass the array
const ramdaUppers = map(longWordBigE, colours)

console.log(`The uppercased colours by Array.map: [${vjsUppers}]`)
console.log(`The uppercased colours by Ramda's map: [${ramdaUppers}]`)
console.log(`Remember, the original array is unchanged: [${colours}]`)

So we can use conditionals or anything we like really, modifying some, all, or none.

Use reduce to combine elements in a list

We've already talked about reduce above. It takes a function, a starting accumulator, and the list to process. It returns the final accumulator.

Above we used reduce first to add a list of numbers together to get a sum. Let's do it again using both the Array's reduce method and Ramda's reduce function:

1
2
3
4
5
6
7
8
9
10
11
12
13
// add(1, 2) returns 3
import { add, reduce } from 'ramda'

const nums = [1, 2, 3, 4, 5]

// Vanilla JS
const vjsSum = nums.reduce(add, 0)

// Ramda
const ramdaSum = reduce(add, 0, nums)

console.log(`The Array.reduce sum is ${vjsSum}`)
console.log(`The Ramda reduce sum is ${ramdaSum}`)

We'll see reduce again and again in the examples below. You can make any of these other functions from reduce.

More fun with reduce

Suppose we want to loop through an object and uppercase every string value. Can we do that with reduce? Easy peasy:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { keys, reduce, toUpper } from 'ramda'

const bob = {
  name: 'Bob',
  age: 'indeterminate',
  iq: 180,
  speaksFrench: true
}

const upcaseStrings = (acc, key) => {
  const value = bob[key]
  
  return {
    ...acc,                          // copy current accumulator
    [key]: typeof value === 'string' // add this key-value pair
      ? toUpper(value)               // uppercase if string
      : value                        // otherwise, pass unchanged
  }
}

const vjsUpcase = Object.keys(bob).reduce(upcaseStrings, {})

const ramdaUpcase = reduce(upcaseStrings, {}, keys(bob))

console.log('Vanilla JS result:', vjsUpcase)
console.log('Ramda result:', ramdaUpcase)

The above example isn't the greatest. We reach outside the reduce function to access our bob object. We should really wrap the function and make a version that takes an object:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { keys, reduce, toUpper } from 'ramda'

// Takes same three parameters as reduce, but last is an object
const reduceOverObject = (f, acc, obj) => reduce(
  (a, key) => f(a, key, obj[key]), // function passed gets value as third parameter
  acc,
  keys(obj)
)

const bob = {
  name: 'Bob',
  age: 'indeterminate',
  iq: 180,
  speaksFrench: true
}

// Now takes the value as a third parameter
const upcaseStrings = (acc, key, value) => ({
  ...acc,                          // copy current accumulator
  [key]: typeof value === 'string' // add this key-value pair
    ? toUpper(value)               // uppercase if string
    : value                        // otherwise, pass unchanged
})

const upcased = reduceOverObject(upcaseStrings, {}, bob)

console.log('Ramda result:', upcased)

Now we're beginning to see the advantage of Ramda. Our new reduceOverObject function takes a function and passes it three arguments: the current accumulator, the current key, and the current value. And we pass it the entire object rather than just an array of the object's keys. Now our function operates only on its parameters and returns a new object as its return value.

Sweet. Everything is neatly contained in my reduceOverObject function. Consider how clean and readable this line is:

25
const upcased = reduceOverObject(upcaseStrings, {}, bob)

Can we duplicate the map function with reduce? Easy!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { map, reduce } from 'ramda'

// As with map, we take a function and an array to apply it to
const reduceMap = (f, arr) => reduce(
  (acc, v) => ([...acc, f(v)]),  // Apply the function to each item in turn
  [],                            // Start with an empty new array
  arr                            // Loop through the original array
)

const nums = [1, 2, 3, 4, 5]

const oldSquares = map(x => x * x, nums)
const squares = reduceMap(x => x * x, nums) // Works the same way

console.log('The squares using map:', oldSquares)
console.log('The squares using our reduceMap:', squares)
console.log('And nums remains unchanged:', nums)

No need to write this function, of course! Just use map. But as you'll see here, reduce is the mother of all these functions. We can recreate virtually all of them with reduce, and we can build even more powerful functions on top of reduce. And reduce is easily created itself by writing a function that calls itself.

Functional code, especially with reuse, is simpler than the OO equivalent. That means fewer lines, drier code, more reusability, and more power. Free your methods! Use functions instead, and treat Objects as types to be passed around, rather than the output of new <ClassName>().

Use filter to choose particular values from a list

The filter method or function takes a function as the first parameter and a list of items to operate on as the second. The function passed as the first argument should accept a value from the list and return a Boolean: true if that value should be included in the output, false if not.

Remember, the output of filter is a new array containing only those items that passed the test.

Suppose we had a list of names and we wanted to find all the names at least 5 letters long. We could do that easily with the filter method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { filter } from 'ramda'

const names = [
  'Bob', 'Carol', 'Ted', 'Alice', 'Samson', 'Delilah'
]

// Vanilla JS:
const vjsLongNames = names.filter(s => s.length > 4)

// Ramda:
const ramdaLongNames = filter(s => s.length > 4, names)

console.log('The longer names by Array.filter:', vjsLongNames)
console.log('The longer names by Ramda\'s filter:', ramdaLongNames)
console.log('The original list is unchanged:', names)

Who needs loops? Not we.

Use adjust to update a single value in a list

What if we want to update an element at a specified index in an array. The mutable way would be simple: names[1] = 'Carole'.

But that mutates our original list. What we want is a new copy of the list with just that element updated. Ramda provides an adjust function to do just this, but we could also write our own version in vanilla JS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { adjust } from 'ramda'

const names = [
  'Bob', 'Carole', 'Ted', 'Alice', 'Samson', 'Delilah'
]

// Vanilla JS: let's create our own function
function vadjust (index, f, arr) {
  return [
    ...arr.slice(0, index),
    f(arr[index]),
    ...arr.slice(index + 1)
  ]
}

const vjsUpdatedNames = vadjust(1, () => 'Carol', names)

// Ramda's adjust
const ramdaUpdatedNames = adjust(1, () => 'Carol', names)

console.log('Update Carole to Carol by vanilla JS:', vjsUpdatedNames)
console.log('Update Carole to Carol by Ramda\'s adjust:', ramdaUpdatedNames)
console.log('The original list is unchanged:', names)

Now you're probably wondering why we might add the Ramda dependency with its load time if we can write our own functions so easily.

Well, it depends on what we need. If our needs are as simple as above, then to write our own is probably the way to go. (But be advised that we are then responsible for testing and maintaining our utility functions as well.)

But the Ramda adjust function can do much more than our version. For one thing, we can apply the arguments one a time, and with each, get back a function that remembers the arguments already supplied and takes the remaining parameters:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { adjust, toUpper } from 'ramda'

const upcaseThirdElement = adjust(2, toUpper)

const names = [
  'Bob', 'Carole', 'Ted', 'Alice', 'Samson', 'Delilah'
]

const colours = [
  'red', 'yellow', 'green', 'cyan', 'blue', 'magenta'
]

console.log('Uppercase the third name:', upcaseThirdElement(names))
console.log('Uppercase the third colour:', upcaseThirdElement(colours))
console.log('The original lists are unchanged:', names, colours)

Ramda's adjust can also handle negative indexes (working back from the end of the list). Our vadjust fails on negative indexes. And if we pass an index that's out of range, then Ramda's adjust returns our list unchanged so we can continue processing. Again, our version fails. We could fix our version:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
function adjust (...args) {
  // If not all arguments supplied...
  if (args.length < 3) {
    // Return a function that remembers the current arguments,
    // takes more arguments, and calls adjust again with both
    return (...newArgs) => adjust(...args, ...newArgs)
  }
  
  // Once we have all three arguments, destructure them
  const [index, f, arr] = args
  
  // If the index is out of bounds, return the array unchanged
  if (index >= arr.length || index < -arr.length ) {
    return arr
  }
  
  return [
    ...arr.slice(0, index),     // Get the elements before the index
    f(...arr.slice(index)),     // Apply the function to the element at index
    ...(
      index === -1
        ? []                    // Handle the special case of -1
        : arr.slice(index + 1)) // Get the remaining elements, if any
  ]
}

const data = [0, 1, 2, 3, 4]

// We can apply arguments in any increments
console.log('adjust(2)(String)(data)', adjust(2)(String)(data))
console.log('adjust(2, String)(data)', adjust(2, String)(data))
console.log('adjust(2, String, data)', adjust(2, String, data))
console.log('adjust(2)(String, data)', adjust(2)(String, data))

// Calling with no arguments returns the function unchanged
console.log('adjust()()(2)()(String)()()(data)', adjust()()(2)()(String)()()(data))

// It works at different indexes
console.log('adjust(3, String, data)', adjust(3, String, data))
console.log('adjust(0, String, data)', adjust(0, String, data))
console.log('adjust(4, String, data)', adjust(4, String, data))

// It works with negative indexes
console.log('adjust(-1, String, data)', adjust(-1, String, data))
console.log('adjust(-4, String, data)', adjust(-4, String, data))

const adjustLast = adjust(-1)
const addTenToLast = adjustLast(x => x + 10)

console.log('addTenToLast(data)', addTenToLast(data))

But now it's on us to test it and keep it current. Much easier to use a library such as Ramda or Lodash/FP!

Use append, insert, or prepend to add an item to a list

These functions do exactly what you'd expect. Should we do it by hand, or use a library? For append and prepend, at least, vanilla JS is probably enoughunless we're pipelining (composing) functions.

The append is the easiest:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { append } from 'ramda'

const nums = [1, 2, 3]

// Vanilla
const vjsManual = [...nums, 4]

// Our own append function
const vappend = (x, arr) => ([...arr, x])

const vjsAppend = vappend(4, nums)

// Ramda
const ramdaAppend = append(4, nums)

console.log('[...nums, 4]:', vjsManual)
console.log('vappend(4, nums):', vjsAppend)
console.log('append(4, nums):', ramdaAppend)
console.log('nums is unchanged:', nums)

The prepend is also very easy:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { prepend } from 'ramda'

const nums = [1, 2, 3]

// Vanilla
const vjsManual = [0, ...nums]

// Our own prepend function
const vprepend = (x, arr) => ([x, ...arr])

const vjsAppend = vprepend(0, nums)

// Ramda
const ramdaAppend = prepend(0, nums)

console.log('[0, ...nums]:', vjsManual)
console.log('vprepend(0, nums):', vjsAppend)
console.log('prepend(0, nums):', ramdaAppend)
console.log('nums is unchanged:', nums)

The insert function is a bit trickier:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import { insert } from 'ramda'

const names = [
  'Bob', 'Carol', 'Ted', 'Samson', 'Delilah'
]

// Ramda returns a copy
console.log('insert(3, \'Alice\', names)', insert(3, 'Alice', names))
console.log('names is unchanged:', names)

// Splice mutates in place: DO NOT DO THIS!
console.log('splice returns []', names.splice(3, 0, 'George'))
console.log('But names has been mutated!', names)

// Let's remove George
// Does this look intuitive to you?
names.splice(3, 1)

// Best method with vanilla JS
const vjsInsert = (index, item, list) =>
  index > -1 && index < list.length
    ? [
        ...list.slice(0, index),
        item,
        ...list.slice(index)
      ]
    : [...list, item]

console.log('Vanilla JS: vjsInsert(3, \'Alice\', names)', vjsInsert(3, 'Alice', names))
console.log('Out of bounds appends, same as Ramda insert (try it):')
console.log('vjsInsert(-1, \'Alice\', names)', vjsInsert(-1, 'Alice', names))
console.log('vjsInsert(7, \'Alice\', names)', vjsInsert(7, 'Alice', names))
console.log('names unchanged:', names)

Use remove to remove an item from a list

Ramda's remove function removes one or more sequential items from a list.

It expects three parameters:

  1. The index at which to start removing items.
  2. The number of items to remove.
  3. The list from which to remove the items

And returns a copy of the list with the items removed.

We can accomplish this in vanilla JS with a little more work. What we want to avoid is using the Array splice method as it mutates the array in place, and we would never do that, would we?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import { remove } from 'ramda'

const names = [
  'Bob', 'Carol', 'Ted', 'Alice', 'Samson', 'Delilah'
]

// Ramda returns a copy
console.log('remove(4, 1, names) removes Samson', remove(4, 1, names))
console.log('names is unchanged:', names)
console.log('remove(4, 2, names) removes Samson and Delilah', remove(4, 2, names))

// Splice mutates in place: DO NOT DO THIS!
console.log('splice returns an array of the removed items:', names.splice(4, 1))
console.log('But names has been mutated!', names)

// Let's reinsert Samson
// Does this look intuitive to you?
names.splice(4, 0, 'Samson')

// Best method with vanilla JS
const vjsRemove = (index, len, list) =>
  index > -1 && index < list.length
    ? [
        ...list.slice(0, index),
        ...list.slice(index + len)
      ]
    : [...list]

console.log('Vanilla JS: vjsRemove(4, 1, names)', vjsRemove(4, 1, names))
console.log('Out of bounds does nothing, same as Ramda remove (try it):')
console.log('vjsRemove(-1, 1, names)', vjsRemove(-1, 1, names))
console.log('vjsRemove(7, 1, names)', vjsRemove(7, 1, names))
console.log('names unchanged:', names)

Use concat or zip to combine two lists

The concat function takes two arrays of equal length and returns an array of arrays, each with two elements: the first from the first array; the second the matching element in the second array. For example:

concat([1, 2], [3, 4]) // yields [1, 2, 3, 4]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import { concat } from 'ramda'

const things = ['a', 'b', 'c']
const moreThings = ['d', 'e']
const andMore = ['f', 'g']

// Ramda's concat is easy peasy
console.log('concat(things, moreThings)', concat(things, moreThings))

// concat only takes two parameters
console.log('concat(moreThings, things, andMore)', concat(moreThings, things, andMore))

// The vanilla JS Array.concat is a bit trickier as you need to call
// it on one of the arrays: it is a method, after all
// But at least it RETURNS A COPY!
console.log('things.concat(moreThings)', things.concat(moreThings))
console.log('moreThings.concat(things)', moreThings.concat(things))

// The Array.concat method takes any number of arrays
console.log('things.concat(moreThings, andMore)', things.concat(moreThings, andMore))

// But we can easily create our own in vanilla JS
const vjsConcat = (left, right) => ([...left, ...right])
console.log('vjsConcat(things, moreThings)', vjsConcat(things, moreThings))
console.log('vjsConcat(moreThings, things, andMore)', vjsConcat(moreThings, things, andMore))

// Or make one that takes any number of arrays
const superConcat = (x, ...xs) => xs.length < 1
  ? x
  : [...x, ...superConcat(...xs)]

console.log('superConcat(things, moreThings, andMore)')
superConcat(things, moreThings, andMore)

The zip function takes two arrays of equal length and returns an array of arrays, each with two elements: the first from the first array; the second the matching element in the second array. For example:

zip([1, 2], [3, 4]) // yields [[1, 3], [2, 4]]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { zip } from 'ramda'

const digits = [1, 2, 3, 4, 5, 6]
const numerals = [
  'one', 'two', 'three', 'four', 'five'
]

// Ramda's zip zips while elements exist in both arrays and drops extras
console.log('zip(digits, numerals)', zip(digits, numerals))
console.log('zip(numerals, digits)', zip(numerals, digits))

// Implementing this is vanilla JS is no mean feat
// Behold the power of reduce!
const vjsZip = (left, right) => left.reduce(
  (acc, val, idx) => right[idx]    // If there is an equivalent element in right
    ? [...acc, [val, right[idx]]]  // zip left and right together
    : acc,                         // else return the accumulator unchanged
  []
)

console.log('vjsZip(digits, numerals)', vjsZip(digits, numerals))

console.log('vjsZip(numerals, digits)')
vjsZip(numerals, digits)

Use times to create a new list

The times function takes a function and a number of times, n, to iterate and returns an array of n length with the result of calling the function with the array index. So:

times(n => n * 2, 5) // yields [0, 2, 4, 6, 8]

Easy, no?

Doing the same in vanilla JS can be a bit more complex.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { identity, times } from 'ramda'

// Ramda's times takes a function and a number n
// Calls the function n times passing it 0, 1, 2, etc. (the index)
// identity is equivalent to x => x
console.log('times(identity, 5)', times(identity, 5))
console.log('times(n => (n + 1) * (n + 1), 5)', times(n => (n + 1) * (n + 1), 5))
console.log(`times(() => '', 3)`, times(() => '', 3))
console.log('times(n => n.toString(2), 12)', times(n => n.toString(2), 12))

// In vanilla JS, we can use the new keyword to instantiate an array and pass a length
console.log('new Array(5) has undefined values:', new Array(5))
console.log('new Array(5).map((_, i) => i) does not work:', new Array(5).map((_, i) => i))
console.log(`new Array(5).fill('').map((_, i) => i) works:`, new Array(5).fill('').map((_, i) => i))

Some equivalencies (there may be easier ways, but we don't know them):

times(identity, 5) // yields [0, 1, 2, 3, 4]
new Array(5).fill(null).map((_, i) => i) // yields [0, 1, 2, 3, 4]

times(n => (n + 1) * (n + 1), 5) // yields [1, 4, 9, 16, 25]
new Array(5).fill(null).map((_, i) => (i + 1) * (i + 1)) // [1, 4, 9, 16, 25]

times(() => '', 3) // yields ['', '', '']
new Array(3).fill('') // yields ['', '', '']

times(n => n.toString(2), 12) // yields binary 0 to 1011
new Array(12).fill(null).map((_, i) => (i + 1).toString(2)) // yields binary 0 to 1011

As you can see, while it can be done in vanilla JS, the Ramda times function is much easier to use. You could, of course, create your own vanilla JS version, but we'll leave that as an exercise for the reader.

Note: The _ (underscore) is often used in functional programming to indicate a value that we don't care about. In the above examples, the Array map method passes the index as the second argument to the function. Here we only care about the index, not the null that is the value at that index, so we use (_, i) to get the index of the element and ignore the value at that index.

It's a bit tricky to just use an underscore (_), but, ha, ha, Ramda to the rescue. Ramda provides a double-underscore (__) that you can import and use in the same way with your Ramda functions.

Use head and tail to destructure a list

Remember that the head of a list is just the first element in the list. If it's a list of numbers, then the head is a number; if a list of strings, then the head is a string.

The tail, in contrast, is the rest of the array, so it is always an array of the same type as the original array, just without the head element. Note: the tail can be empty, but it is never undefined.

If we try to get the head of an empty array, it will be undefined.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import { head, tail } from 'ramda'

const nums = [1, 2, 3, 4, 5]

// In vanilla JS we can use Array.slice to get the head and tail:
const x = nums[0]
const xs = nums.slice(1)

console.log(`x is ${x}`)
console.log(`xs is ${JSON.stringify(xs)}`)
console.log('nums is unchanged:', nums)

// We can also use destructuring, which is often easier and clearer
const [y, ...ys] = nums

console.log(`y is ${y}`)
console.log(`ys is ${JSON.stringify(ys)}`)
console.log('nums is unchanged:', nums)

// Look what happens with an empty list:
const [z, ...zs] = []

console.log(`z (the head of []) is ${z}`)
console.log(`zs (the tail of []) is ${JSON.stringify(zs)}`)

// Ramda also offers head and tail functions
const r = head(nums)
const rs = tail(nums)

console.log(`r is ${r}`)
console.log(`rs is ${JSON.stringify(rs)}`)
console.log('nums is unchanged:', nums)

Frankly, this is one area where Ramda's functions don't always add much value. As with prepend and append, it is usually easier to use destructuring. Where Ramda's head and tail functions are very useful is in pipelined functions, discussed in We can has pipelines!

Use all, any, or none to test a list

The all takes a function and a list, applies the function to each item in the list, and returns true if and only if the function passed returns true for each and every item in the list.

The vanilla JS equivalent is the Array every method.

1
2
3
4
5
6
7
8
9
10
11
import { all } from 'ramda'

const tests = ['Bob', 'Sally', 'Samuel', 'Ruth']

// Vanilla JS
console.log('tests.every(name => name.length > 1)', tests.every(name => name.length > 1))
console.log('tests.every(name => name.length > 4)', tests.every(name => name.length > 4))

// Ramda
console.log('all(name => name.length > 1, tests)', all(name => name.length > 1, tests))
console.log('all(name => name.length > 4, tests)', all(name => name.length > 4, tests))

The any takes a function and a list, applies the function to each item in the list, and returns true if and only if the function passed returns true for at least one item in the list.

The vanilla JS equivalent is the Array some method.

1
2
3
4
5
6
7
8
9
10
11
import { any } from 'ramda'

const tests = ['Bob', 'Sally', 'Samuel', 'Ruth']

// Vanilla JS
console.log('tests.some(name => name.length > 4)', tests.some(name => name.length > 4))
console.log('tests.some(name => name.length > 10)', tests.some(name => name.length > 10))

// Ramda
console.log('any(name => name.length > 4, tests)', any(name => name.length > 4, tests))
console.log('any(name => name.length > 10, tests)', any(name => name.length > 10, tests))

The none takes a function and a list, applies the function to each item in the list, and returns true if and only if the function passed returns false for every item in the list .

The vanilla JS equivalent is the Array every method, but with the function changed to return true when the condition is not met, which is pretty non-intuitive. But there doesn't appear to be a true vanilla JS equivalent of none.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { none } from 'ramda'

const tests = ['Bob', 'Sally', 'Samuel', 'Ruth']

// Vanilla JS
console.log('tests.every(name => name.length <= 1)', tests.every(name => name.length <= 1))
console.log('tests.every(name => name.length <= 4)', tests.every(name => name.length <= 4))

// Or perhaps more clearly
console.log('tests.every(name => !(name.length > 4))', tests.every(name => !(name.length > 4)))
console.log('tests.every(name => !(name.length > 10))', tests.every(name => !(name.length > 10)))

// Ramda
console.log('none(name => name.length > 4, tests)', none(name => name.length > 4, tests))
console.log('none(name => name.length > 10, tests)', none(name => name.length > 10, tests))

Of course, all of the Array methods are methods, so they must be called on the array, which makes pipelining functions more difficult, as we'll see. The Ramda functions are true functions.

Use flatten to flatten nested lists into a single list

This is a simple way to turn this:

1
[[1, 2, 3], [4, 5], [6, 7, 8]]

Into this:

1
[1, 2, 3, 4, 5, 6, 7, 8]

Let's try it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { flatten, unnest } from 'ramda'

const nested = [[1, 2, 3], [4, 5], [6, 7, 8]]
const deeplyNested = [1, [2, [3, [4, [5, [6]]]]]]

// Ramda flatten does so recursively
console.log('flatten(nested)', flatten(nested))
console.log('flatten(deeplyNested)', flatten(deeplyNested))

// Ramda unnest flattens just one level deep
console.log('unnest(nested)', unnest(nested))
console.log('unnest(deeplyNested)', unnest(deeplyNested))

// Vanilla JS flatMap workes like unnest
console.log('nested.flatMap(n => n)', nested.flatMap(n => n))
console.log('deeplyNested.flatMap(n => n)', deeplyNested.flatMap(n => n))

// Vanilla JS flat works a bit like unnest, but it can specify the
// levels of nesting to flatten
console.log('deeplyNested.flat(3)', deeplyNested.flat(3))
console.log('deeplyNested.flat(5)', deeplyNested.flat(5))

console.log('deeplyNested remains unchanged:', deeplyNested)

In this unusual case, working with partially unnesting arrays is actually easier with the Array flat method. Of course, because the method returns a copy, we could always create our own.

Here use Ramda's curry function (more on this later) to make it possible to apply the arguments one at a time. This gives us the same power as Ramda's functions to be partially applied and then used in pipelined functions:

1
2
3
4
5
6
7
8
9
10
11
12
import { curry } from 'ramda'

const nested = [[1, 2, 3], [4, 5], [6, 7, 8]]
const deeplyNested = [1, [2, [3, [4, [5, [6]]]]]]

const unnest = curry((n, list) => list.flat(n))

console.log('unnest(3, deeplyNested)', unnest(3, deeplyNested))
console.log('unnest(5, deeplyNested)', unnest(5, deeplyNested))

const unnestTwoLevels = unnest(2)
console.log('unnestTwoLevels(deeplyNested)', unnestTwoLevels(deeplyNested))

Avoid forEach! Avoid forEach! Avoid forEach!

The forEach method on the Array (andgasp!the forEach function in Ramda) is used entirely for side effects. Say it ain't so, Ramda!

Side effects almost always mean mutation. And that leads directly to the zombie apocalypse. Avoid.

Get thee behind me, Satan!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// EVIL! PURE EVIL!
// forEach is used only for side effects

const output = ['a', 'b', 'c'].forEach(
  (value, index, array) => {
    console.log(`The value is ${value}`)
    console.log(`The index is ${index}`)
    console.log(`The array is [${array}]`)
  }
)

// How can forEach do anything if the result is undefined?
// Only by side effects. Ugh.
console.log(`The output of Array.forEach is ${output}`)

Every time you use forEach, a kitten dies. A really adorable one. Just sayin'.

Next, we'll talk about pipelines.

Errors, bugs, suggestions, questions? Spare paper bags filled with unmarked fifties and hundreds? Contact Charles Munat.