Immutability
Normally when we want to change a property of an object, we assign it directly:
The object is said to mutate, since its internal properties are changing, but the myObj
reference stays the same. The identity of the object is the same after its value has changed. Immutable is when the value or contents of an object never changes.
This pretty common-sense: a lot of the objects of the everyday world are mutable. If someone breaks their arm, it’s not like they become a new person.
However in code, this can lead to some confusion for two reasons.
- References appear to change unexpectedly
- It’s hard to reason about where and why changes happen
Mysterious changes
Let’s look at an example for the first case.
On line 3 & 4 we have two variables pointing at the same object. Because the names of the variables are different, it’s easy to think that they refer to different things.
When we change b
on line 5, we’d expect to just change b
, but what happens is that a
, b
and myObj
all change:
This is because myObj
, a
and b
are all the same object, they have the same identity. A naive reading of the code may give you to think there are three objects (myObj
, a
and b
) but there’s really only one, with all three variables pointing at the same thing: a === b === myObj
.
Another problem with mutability is when passing objects to a function. The function might change the object in unexpected ways, you have no assurances of what it does. When object references get used in different areas of the code all mutating the object it can very easily be confusing: ‘who’ is changing the object? Why are they? When are they? etc.
Making things immutable
Let’s revisit the simple example, but work in an immutable way.
Now we aren’t changing the values of b
, we are creating a new object with the spread syntax and assigning back to a
.
This is better! Now only b
is changing, just as we’d expect from a casual look at the code.
However, at present there’s nothing to stop us from mutating the object. We could still write b.value = ...
Freeze for safety
There are discussions about adding more mechanisms for immutability in Javascript, but for now we have Object.freeze
.
Give it an object, and it will give you back a ‘frozen’ version such that an error is thrown if you try to modify it.
Ok, we’ve stopped ourselves from changing the object. To change it we have to reassign as before, this time making sure also freeze the new object too:
Now we have stronger guarantees of code safety. If you’re using Typescript, this sort of thing can be expressed very easily without all the Object.freeze()
going on.
It can be rather ugly to use Object.freeze()
all the time, so perhaps use it for key objects that you don’t want to risk changing unexpectedly. In the ixfx demos, we use that for state and settings.
Copying immutable objects
To copy immutable objects, you can just skip assigning properties:
Comparing immutable objects
A gotcha with immutability is that objects can’t be compared using the usual equality operator (===
).
Instead, you need to compare by values.
ixfx has helper functions for comparing values.
In Typescript
If you’re using Typescript, soft immutability is easy (soft in the sense it’s not enforced at runtime).
We don’t need to use Object.freeze()
, as the Typescript compiler will stop us from breaking the rules.
The Readonly
utility type can wrap most things, also arrays.