Why and how to Bring Immutability to JS World (Inspired by Scala)

Alexey Balchunas
5 min readOct 7, 2018

--

JS is good enough to try functional programming.

I’ve been working with Scala for a while now, and this is just a remarkable programming language. If you’re not familiar with functional programming, but you’d want to at least be aware of it (or Scala in particular), I’d recommend to take a look at the Martin Odersky’s free course.

Before we go any further I want to mention why functional programming often goes closely with immutability. The thing is that one of the most important things of functional programming is pure functions.

Pure functions (or expressions) have no side effects (memory or I/O).

This basically means that despite anything such a function will return the same result when executing it with the same arguments. Now, imagine any data structure, if you call a method of it, how do you guarantee that the result is always the same? How do you make sure that result of calling the method add of a List ends up with the same result every time? That’s right, you just do not mutate the object, ever. Here is where immutability comes.

If you like this topic, you should definitely check out this one as well: Top 5 Headaches I Had With NodeJS (as a Java Developer).

Why?!

The answer is not very obvious for ones that are used to plain old mutable JS arrays and objects. I’m sure that if I had no experience in functional programming, I’d never think about immutability at all because arrays and objects are good enough for anything.

Here is the perfect and concise description of why you should consider practicing functional programming (taken from immutable.js repo):

Immutable data cannot be changed once created, leading to much simpler application development, no defensive copying, and enabling advanced memoization and change detection techniques with simple logic. These data structures are highly efficient on modern JavaScript VMs by using structural sharing via hash maps tries and vector tries as popularized by Clojure and Scala, minimizing the need to copy or cache data.

I’ve also come up with a short list of advantages of using functional programming in general and in JS particularly:

  1. Mutations are bad. Imagine the world where everything is immutable. Seriously, it’s MUCH easier to reason about your own code: you pass some objects as arguments somewhere and you’re 100% sure they won’t be changed by the called function, because “nothing can be mutated” is invariant, period. No unexpected side effect would be able to hide from you (a.k.a. bugs).
  2. Sharing is safe. I know, JS has no threads. Even though race conditions may still happen and your code may easily become tricky enough so that you have to think 7 times before using some shared object. If the object is immutable, you are free to do whatever you wish, it will not affect other parts of the code using this object as well.
  3. Easy mutation tracking. Immutable objects are more like primitives (numbers, strings, dates, etc.), so, for example, you can track if a variable is changed simply by checking for objects equality.

How?

Ok, now you’re all excited about functional programming and ready to try it out. Feel free to use my following tips:

Immutable.js

The biggest part of any codebase is probably handled by collections: Map, Set, List, Stack, Iterable, etc. Thus, the first thing you will need is Immutable.js.

Immutable.js provides many Persistent Immutable data structures including List, Stack, Map, OrderedMap, Set, OrderedSet and Record. These data structures are highly efficient on modern JavaScript VMs by using structural sharing via hash maps tries and vector tries as popularized by Clojure and Scala, minimizing the need to copy or cache data. Immutable.js also provides a lazy Seq, allowing efficient chaining of collection methods like map and filter without creating intermediate representations. Create some Seq with Range and Repeat.

Now, the biggest difference from the plain old mutable arrays and objects is that the “mutating” methods do not actually change objects, instead they simply return the updated instance leaving the original object untouched. For example:

const { Map } = require('immutable');
const map1 = Map({ a: 1, b: 2, c: 3 });
const map2 = map1.set('b', 50);
map1.get('b') + " vs. " + map2.get('b'); // 2 vs. 50

Creating Immutable Classes

Okay, we have the immutable collections, but how do you actually use them in your code? I bet your codebase has classes, so the creation of your own immutable classes is the first problem you’ll face, and the immutable collections do not help much with this particular issue. Here is how I’ve solved it.

The first idea was the right one: just replicate Scala’s copy method. Basically, all you need is just a super simple utility base class. Something like this (written in TypeScript):

export abstract class CloneWith {
cloneWith(mutate: (t: this) => void): this {
const cloned: this = Object.create(this);
keys(this).forEach((k) => cloned[k] = this[k]);
mutate(cloned);
return cloned;
}
}

Then you just use the cloneWith method for mutating actions on your immutable class. Here is a dummy example (TypeScript as well):

class ImmutableHouse extends CloneWith {
constructor(
readonly address: string,
protected owner: string
) {
super();
}
buy(newOwner: string): ImmutableHouse {
return this.cloneWith((house) => {
house.owner = newOwner;
});
}
getOwner(): string {
return this.owner;
}
}

const house = new ImmutableHouse("Street", "Owner");
const updatedHouse = house.buy("New Owner");
house.getOwner() + " vs " + updatedHouser.getOwner(); // Owner vs New Owner

As you can see, cloneWith is tiny, so you can copy and paste it for your project as is.

OR, just use the corresponding npm.js package that I made: blinsky. The library has the mentioned CloneWith base class, but also there are many other useful features. Feel free to use.

Not Everything Has to Be Immutable

The pain old mutable JS arrays and objects are the first-citizens, so you will definitely face many issues related to some collections incompatibilities (e.g. code expects a Set but gets an array). I just want to mention, that you should not try very hard to use immutable collections and classes everywhere. Just use them where you can easily do that. Doing otherwise is not worth it.

One more important note for the souls that are very confused by this whole article: not everything in your code should be immutable because you just won’t be able to avoid all side effects everywhere. For example, users still need to store their data somewhere, it needs to be mutated, so side effects are not completely avoidable.

What’s Next?

How do you actually store and read your class (not a JS object) to and from a data store? If you really want to know, don’t miss the next article on this topic. Subscribe to the mailing list and follow me on medium.

Also, besides these articles, I share many tips and thoughts to subscribers of the mailing list only, so consider subscribing.

Comments, likes, and shares are highly appreciated. Cheers! ❤️

--

--