Alex's Blog

Alex's Blog

Mutability in Code

Mutability in Code
Featured on Hashnode

In this article, we will talk about mutability, what it means, and the different trade-offs relevant to it when programming.

What is mutability?

A mutable value is one that can change during the execution of the program. It means that we can create a value, bind it to a variable, reassign the variable, or change parts of the value. We can see how it works in the following Javascript snippet:

image.png

Notice that we talk about values and not variables because they are not the same. Having, in memory, an object that can change/be changed. In contrast, having a mutable variable is just saying that our access point to the values is mutable. In the snippet above, the variable is mutable and some of the values are { foo: 3 }, { willChange: 4 }, and 2. In practice, mutability is handled by the compiler, so we'll be talking about both interchangeably most of the time.

On the other hand, we have immutability, which means the inability of a value/variable to be changed. Some examples of immutable values that we often find in programming languages are strings and dates. The reader might think: What use does a variable I cannot change have? Well, it may come as a surprise, but there are programming languages where everything is immutable.

Most of the time, we hear mutable this and immutable that, but no one takes the time to explain why we should understand the trade-offs. It is very relevant to know when to use mutable or immutable patterns, since this can remove whole classes of bugs or make the code more readable and concise.

If one part of our code operates on the assumption that a value will never change and another part of our code changes that value, it's possible that the first part of the code won’t do what it was designed to do. The cause of this kind of bug can be difficult to track down after the fact, especially when the second piece of code changes the value only sometimes.

But first, let's see some ways we can encounter mutability in the wild.

Flavors of mutability

We can find mutability at the lowest level. In assembly languages we interact with memory, mutating it the way we want without restrictions. It is relevant because we have to understand that mutation is a high-level concept. We don't talk about mutability at the assembly level: everything is mutable. The whole basis of computers is changing 0s to 1s.

At a higher level, there are programming languages where we cannot enforce immutability, but in specific cases or built-in types. Python, for example, has no way of doing this out of the immutable built-in types. If we take a look at Python's tuples, we can see they are immutable, but with a catch:

image.png

It might be obvious to some, but we could argue whether it makes sense or not. Are tuples truly immutable?

Another example of this is Javascript const variables. There has been a lot of controversy around whether to use let or const, and we will not get into it. But one thing is clear: const does not mean immutability. At most, it means that the name is immutable (more like read-only). We cannot reassign to a variable declared with const, but we can certainly mutate the values bound to that variable that have a level of indirection:

image.png

What we mean by indirection is that the reference is immutable, but the value that the reference points to is not. Are these variables truly immutable?

Other languages like C# and C++ have mutable values by default. However, there are programming constructs and techniques that enforce immutability. In C#, we can effectively implement custom immutable types as shown elegantly in Eric Lippert's fabulous blog.

Other programming languages allow both mutable and immutable variables, like F# and Rust. Those two have immutable variables by default, and only after adding certain keywords, a variable is effectively mutable. It is worth noting that mutability gets enforced by the compiler in the sense that it disallows operations of this kind:

image.png

The snippet above will not compile because the value is immutable, and we are trying to add an element to the vector. In this case, the solution is to make the variable mutable with the mut keyword.

image.png

Finally, in languages like Haskell and Clojure, all of the values are immutable. That means that there is no way of mutating a value after creation.

We can find this concept at a higher level. Some libraries implement immutability as a first-class citizen.

Take ReactJS, for example. This paragraph merges a bit the concept of pure functions with immutability, but we will try to stay in scope. ReactJS is a library for building user interfaces. The essence of it is that UI is a function of state. That sounds abstract but is good enough. The library maintains some state and renders the UI. We can say that render means paint to the browser, while the state is Javascript variables managed by the library. The relevant issue for us is that the state is immutable. To mutate the state we use an updater function provided by the library, and we can only mutate the state by using this function. With this constraint, ReactJS guarantees to build the UI deterministically.

But still, the scale gets wider. We can talk about immutable infrastructure, and approach to deploying software where changes in place are not allowed. Instead, new deployments substitute the old ones. Very much like making every deployment immutable.

Why do we care?

OK, so we know at what levels we can find mutability, but why do we care? Well, being mutable changes the semantics of the program and has repercussions. See the following examples:

In the following snippet of code, what is the value of whatAmI after calling sus?

image.png

Well, it depends. We would have to check the code of sus to know the value of the variable. What if whatAmI was immutable? Then, we wouldn't care about the implementation of sus because we know that it just reads the variable. This is a good read for those who want to see more examples of how mutability can be worrisome.

On the other hand, look at the following comparison. We have the same semantics, first written with immutability in mind, and then written while allowing mutation.

image.png

Writing update logic like this can be hard, and it is easy to miss a section that you did not copy. Functional programming languages deal with this in various ways. However, using an imperative, mutable style when writing update logic might be the better way.

Immutability is a bit underused

Some of the perks of working with immutable values:

  • Often, immutability makes more sense. When we change the value 1 or add a year to a Date, the values don't cease to exist. Instead, we have new values altogether.
  • Making copies of immutable values is very cheap and fast. We can return the same value since it cannot change every section of the program can refer to this particular value.
  • We can save memory with immutable values. If we had 100 instances of a string, they could change to refer to the string.
  • Immutable values are inherently thread-safe. Being unable to change removes the occurrence of data races.

Those are just a few, but you can see that immutability comes with several advantages. However, I think that the trade-off is that immutable semantics are harder to write, but easier to debug and reason about than their counterpart.

So why not combine the best of both worlds? It is what languages like Rust and F# do. Imagine enjoying the good things about immutability while being able to opt-out when you need it. Is this the best scenario?

 
Share this