I started a series on functional programming on the X-Team blog with a goal to make the perplexing functional paradigm in JavaScript fathomable. Immutability plays a vital role in creating pure functions for functional programming and it was only covered in brief.
After bringing up the topic of immutability in a slack conversation, the first response I got was this:
The use of the const
keyword in modern JavaScript has been assumed by many as a way to preserve immutable data. This notion is right to an extent but wrong in regards to the way functional programmers perceive immutable code.
The problem with the const
keyword is that it doesn’t create immutable values but rather, it creates immutable assignments which are only of little benefits when compared to value immutability. Here’s what I mean:
const foo = 'I am Thor the god of thunder'
foo += ' and my brother Loki is the god of mischief'
console.log(foo) // I am Thor the god of thunder
The operation above is the same as
const foo = 'I am Thor the god of thunder'
foo = 'I am Thor the god of thunder and my brother Loki is the god of mischief'
console.log(foo) // I am Thor the god of thunder
and both will raise reassignment errors. Whereas, foo
will be successfully reassigned if we had used var
or let
. This (const
) is good for primitive types (null, undefined, number, string) with already immutable values, but it can’t help much when using Arrays and Objects
const bar = ['Thor', 'Asgard']
bar[0] = 'Loki'
console.log(bar) // ['Loki', 'Asgard']
const Odin = {
hela: 'death',
thor: 'hammer',
loki: 'mischief'
}
Odin[thor] = 'thunder'
console.log(Odin) // {hela: 'death', thor: 'thunder', loki: 'mischief'}
Complex datatypes get mutated because they can be modified without reassignment. Pure functions can’t operate on array or object inputs without safely cloning them first, as I’ve explained in the functional programming primer.
To achieve value immutability with object values, we can simply apply a freeze.
const bar = Object.freeze(['Thor', 'Asgard'])
bar[0] = 'Loki'
console.log(bar) // ['Thor', 'Asgard']
In strict mode, reassignment attempts will raise a type error. This solves small usecases as a shallow freeze. It is shallow because this can still be done
const avengers = Object.freeze(['Captain America', 'Iron Man', 'Black Widow', ['Thor', 'Loki']])
avengers[3][0] = 'Spiderman'
avengers[3][1] = 'Antman'
console.log(avengers) // ['Captain America', 'Iron Man', 'Black Widow', ['Spiderman', 'Antman']]
Languages with truly deep immutability usually offer persistent data structures like List, Stack and more types that inherently provide deep immutability. We can employ libraries like immutable.js, mori and seamless-immutable to get some of those benefits in JS without having to reinvent the wheel.
An obvious benefit of immutability in functional programming is in how it helps in maintaining/creating purity in functions. But it’s just as much an important factor for thread safety and an aid for memoization.
Even when the languages we use are not strictly immutable, by trying to reduce the immutability throughout our code, we end up with an easier to maintain and debug code. Loose coupling goes in tandem with immutability in a lot of cases.
Happy Holidays 🎄🎅🏽🥛