How to FP

ReusabilityVariablesLoopingPipelines

Home»Bottom-up»Variables

Variables are set and forget

One fun feature of JavaScript is that variables are not statically typed. That means not only that we can reassign variables or mutate their values, but also that we can change their type.

We know what you're thinking! Whoa, that's insane, right? We can mutate not only the value of a variable but also the type? No way!

But we kid you not, JavaScript actually considers this a feature. Go figure.

Here is an example: We could declare a variable with let, then later assign a number as its value, and then later still assign a string to the same variable:

Don't give in to let. That leads to the dark side. Once you start down the dark path, forever will it dominate your destiny.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// DO NOT DO ANY OF THIS. JUST DON'T.
try {
  // `x` does not yet exist so this will throw and Error
  console.log(`x === ${x} (x is not declared)`)
} catch (e) {
  console.log(e)
}

let x // declares x but does not assign a value

console.log(`x === ${x} (\`x\` has been declared but not assigned a value)`)

x = 7 // assigning the number 7 to x

console.log(`x === ${x} (\`x\` is a number type)`)

x = 'seven' // reassigning x to the string 'seven'

console.log(`WTF? Now x === '${x}' (\`x\` is a string type). Is that crazy or what?`)

If you were looking for an effective way to create hard-to-find bugs in your code, you'd be hard-pressed (heh) to find a better one. If variables can change not only value, but also type, then how can you be sure what the value or type of any variable is anywhere in your code?

In short, if you enjoy chasing down bugs due to unexpected mutationshave fun with that!then let and mutability are for you!

In functional JavaScript, we assign values when they are declared, then we never change them. No, really. Never.

How do we manage this? Read on.

Use const exclusively!

As a start, we prevent variables from being reassigned. We do this with const, a misnamed keyword. It is misnamed because it does not actually create a constant, but only prevents re-assignment. More on this below.

Here is const in action. Note: there is never a good reason to use let or, heaven forbid, var in your applications. (If you're building a library, on the other hand, then let's talk.)

1
2
3
4
5
6
7
8
9
10
11
12
13
const x = 7

try {
  x = 'seven'
} catch (e) {
  console.log(`Can't change the type of x: ${e}`)
}

try {
  x = 3
} catch (e) {
  console.log(`Can't change the value of x: ${e}`)
}

Assign variables when they are declared

We said that const can't be reassigned, so if we declare it without assigning a value, then we're stuck. In the real world, JavaScript usually won't let you do this (depends on the interpreter). So we have to assign the value at the point of declaration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// We can declare a variable with let:
let x

// And then assign it:
x = 'let is evil'

// But let's not! (heh, heh)
console.log(`\`x\` === '${x}'`)

// This does not work! And no, a semicolon won't save you.
// Uncomment the next line to see what happens. (Hint: it breaks)
// const y; y = 'We broken'

// Declare and assign at the same time
const z = 'We cool'

console.log(`Assigning const when declaring works! \`z\` === '${z}'`)

Once a variable has been assigned, don't be changing it

Unreassignable is not the same thing as immutable (unchangeable). If a const is assigned a complex value, such as an Array or Object, then it is possible to reach into that complex value and change it without reassigning the variable. This is a significant failing of const in our not-really-so-humble opinion. It can give one a false sense of security.

Show Evil Terminology

JavaScript does offer, however, a way to reach into objects and make them read-only. Now they really are immutable. Unfortunately, this capability only freezes one level at a time, so to fully freeze an object, we have to traverse the entire tree freezing all the levels.

“Take thy beak from out my heart, and take thy form from off my door!” Quoth the Raven “Nevermore.”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// NOT WHAT YOU MIGHT EXPECT

const obj = Object.freeze({
  name: 'Bob',
  nicknames: ['J.R.', 'Dobbs'],
  favourites: {
    colour: 'blue',
    number: 7,
    value: 'slack'
  }
})

obj.name = 'Sam'
console.log(`Name changed to Sam? Name: '${obj.name}'. No. Good!`)

obj.nicknames[2] = 'Bobby'
console.log(`Append Bobby to the nicknames? Nicknames: '${obj.nicknames.join(', ')}'. Well, crap.`)

obj.favourites.colour = 'purple'
console.log(`Change favourite colour to purple? '${obj.favourites.colour}'. Sob...`)

Well, now, ain't that a damn shame. Mutations be getting all up in them nested objects.

To make this easier, a few people have written libraries and utility functions, such as deep-freeze. Or we can just write a function that calls itself and traverses the object freezing each level in turn:

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
// Object.freeze stupidly mutates in place instead of returning a copy. Doh!
function freezeRecursively (obj) {
  Object.freeze(obj)
  
  Object.getOwnPropertyNames(obj).map(
    prop => {
      if (typeof obj[prop] === 'object') {
        freezeRecursively(obj[prop]) // here the function calls itself
      }
    }
  )
}

// So we will make a copy and freeze that using our above function
function freeze (obj) {
  const out = { ...obj }
  
  freezeRecursively(out)
  
  return out
}

// Allowing us to use it like this
const obj = freeze({
  name: 'Bob',
  nicknames: ['J.R.', 'Dobbs'],
  favourites: {
    colour: 'blue',
    number: 7,
    value: 'slack'
  }
})

obj.name = 'Tom'
console.log(`obj.name !== 'Tom': Still '${obj.name}'.`)

obj.nicknames[2] = 'Bobby'
console.log(`obj.nicknames does not include 'Bobby': Still '${obj.nicknames.join(', ')}'.`)

obj.favourites.colour = 'red'
console.log(`obj.favourites.colour !== 'red': Still '${obj.favourites.colour}'. Yay!`)

Unfortunately, these fail to mutate silently rather than throwing an error. Sigh... nothing is perfect.

Truth is that with best practices and making it a habit to keep things immutable, you probably don't need a freeze function except in unit tests. In your unit tests, you most definitely should ensure that you aren't mutating variables.

Don't erase state: create new state instead!

It might not be clear yet, but refusing to change a variable once it has been assigned is actually about preserving state.

Think about it. Suppose I use let to create a variable x and assign it value 3. (Don't, but hypothetically...)

Now a bit later, I assign it a new value. Say, 5. OK, what was the previous value?

You might remember, but your code doesn't, because you overwrote it. Now why you wanna destroy that state? What did it ever do to you?

In functional JavaScript we keep track of state, in general, by making new copies when we want to change something. This means that if I go back to look at the value of x for any reason, then it is still 3. My newX is 5.

This also allows us to set up time-travel debugging, where we can move a slider back and forth and see how the state of our application changes over time. Often, it means that we can replay everything, too. It almost makes debugging fun.

Almost.

Speaking of state, one of the reasons we keep our variables immutable and our functions pure is to avoid sharing state. That's kind of a mantra for functional programmers: no shared state.

Sharing state is a good way to create difficult-to-find bugs. And it makes parallel processing difficult. When each process is responsible for its own state, life is sweet.

So think about state whenever you write code. After all, every application manages some kind of state. If nothing ever changed, then what would be the point of the app? Well, we suppose you could stare at it all day like a piece of art, but surely art suffices for that purpose...

Use destructuring and the rest/spread operators to make copies

We've shown how we can avoid reassignment of variables by using const, and how we can avoid mutation by making copies, and we've mentioned the spread (and rest) syntax. Let's take a closer look at our syntax options.

The rest syntax and the spread syntax look the same. Both use an ellipsis: ... So what's the difference? It's fairly simple: the rest syntax is used on the left-hand side of an expression, and the spread syntax is used on the right-hand side.

Check it out:

1
2
3
4
5
// The rest syntax is used to collect multiple values to a single variable:
const [x, ...xs] = [1, 2, 3, 4, 5]

console.log(`\`x\` === ${x}`)
console.log(`\`xs\` === ${JSON.stringify(xs)}`)

See if you can make heads or tails out of that. (Yes, that's a joke.)

Remember that JavaScript arrays preserve ordering. You can think of the left-hand side of the assignment operator as a pattern or mask for the right-hand side. By placing x in the position of the first element of the array, it is assigned the first element of our [1, 2, 3, 4, 5] array, which is 1.

Now we want to scoop up the remaining elements of the arraythe rest of themand put them in another variable. The custom of using x and xs for the head and the tail of an array is very common and well understood in functional programming (not just in JavaScript). So we use ...xs to collect all the remaining elements save the first of [1, 2, 3, 4, 5] and put them in an array, which is [2, 3, 4, 5].

We call x the head of our array, and xs the tail. Note that the head has the type of the first element in the array, whereas the tail is always an array of elements (even when empty).

We can also use the rest syntax to collect key-value pairs from an object:

1
2
3
4
5
6
7
8
9
10
11
const {
  colour, ...rest
} = {
  colour: 'pink',
  food: 'Tiramisu',
  show: 'Game of Thrones',
  song: 'Swimming Pools (Drank)'
}

console.log(`\`colour\` === ${colour}`)
console.log(`\`rest\` === ${JSON.stringify(rest)}`)

Unfortunately, Runkit doesn't support this, but you can copy and paste the above into the Chrome Developer Tools console and see that it works. You should find that there is a variable colour with the value 'pink', and a variable rest with the value:

1
2
3
4
5
{
  food: 'Tiramisu',
  show: 'Game of Thrones',
  song: 'Swimming Pools (Drank)'
}

Now let's consider the spread syntax. When we use ... on the right-hand side of the assignment operator, it takes a collection of valuesit could be either an array or an objectand spreads them out into a new array or object.

Here we see the spread syntax in action with an array:

1
2
3
4
5
6
7
8
9
10
11
12
// The spread syntax is used to distribute multiple values from a collection:
const digits = [0, 1, 2, 3, 4, 5]
const moreDigits = [6, 7, 8, 9]

console.log(`[...digits, ...moreDigits] === ${JSON.stringify([...digits, ...moreDigits])}`)
console.log(`[5, ...moreDigits] === ${JSON.stringify([5, ...moreDigits])}`)
console.log(`[...digits, 6, 7] === ${JSON.stringify([...digits, 6, 7])}`)
console.log(`[-1, ...digits, 6] === ${JSON.stringify([-1, ...digits, 6])}`)

// We're making copies!
console.log(`digits is not changed:`, digits)
console.log(`moreDigits is not changed:`, moreDigits)

And here with 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
// The spread syntax works with objects as well:
const rgb = {
  red: 'f00',
  green: '0f0',
  blue: '00f'
}
const cmyk = {
  cyan: '0ff',
  magenta: 'f0f',
  yellow: 'ff0',
  black: '000'
}

// No overlap here
console.log(`{ ...rgb, ...cmyk } is`, { ...rgb, ...cmyk })

// We can overwrite individual values
console.log(`{ ...cmyk, cyan: '0FF', yellow:  'FF0' }`, { ...cmyk, cyan: '0FF', yellow:  'FF0' })

// Ordering is important!
console.log(`{ cyan: '0FF', ...cmyk, yellow:  'FF0' }`, { cyan: '0FF', ...cmyk, yellow:  'FF0' })

// We're making copies!
console.log(`rgb is not changed:`, rgb)
console.log(`cmyk is not changed:`, cmyk)

The ordering is important. As you can see from the code example above, when overwriting, whichever comes last, wins.

One other common use for the rest syntax is to collect arguments into an array. With the rest syntax, the variable name with the ... must come last. It's greedy. If you put it first, it will collect everything and there won't be any left:

1
2
3
4
5
6
7
8
9
10
11
12
// The rest syntax can collect arguments to a function
// Note that it must always be the last destructured variable

function oneAttaTime (x, ...xs) {
  console.log('The value of x is', x)
  console.log('The value of xs is', xs)
}

// Uncomment this to see that it won't work
// function wrongOrder (...xs, x) { console.log('oops') }

oneAttaTime('first', 'second', 'third')

The rest syntax is also great when destructuring props as they come into a React component. Sadly, Runkit doesn't support this, but try it in the DevTools console:

1
2
3
4
5
6
// Imagine that this is a React component
function example ({ name, ...props }) {
  console.log('name is', name)
  console.log('props is', props)  
}
example({ name: 'Bob', nickname: 'J.R.', seeks: 'slack' })

So useful. Now that we understand how functions provide high reusability and variables should be set and forget, let's see how we can avoid loops.

Errors, bugs, suggestions, questions? Contact Charles Munat.