Scala programming best practice: Prefer immutable variables (values)

This is an excerpt from the Scala Cookbook (partially modified for the internet). This is Recipe 20.2, “Scala programming best practice: Prefer immutable variables (values).”

Problem

You want to reduce the use of mutable objects and data structures in your code.

Solution

Begin with this simple philosophy, stated in the book, Programming in Scala:

“Prefer vals, immutable objects, and methods without side effects. Reach for them first.”

Then use other approaches with justification.

There are two components to “prefer immutability”:

  • Prefer immutable collections. For instance, use immutable sequences like List and Vector before reaching for the mutable ArrayBuffer.
  • Prefer immutable variables. That is, prefer val to var.

In Java, mutability is the default, and it can lead to unnecessarily dangerous code and hidden bugs. In the following example, even though the List parameter taken by the trustMeMuHaHa method is marked as final, the method can still mutate the collection:

// java
class EvilMutator {
    // trust me ... mu ha ha (evil laughter)
    public static void trustMeMuHaHa(final List<Person> people) {
        people.clear();
    }
}

Although Scala treats method arguments as vals, you leave yourself open to the exact same problem by passing around a mutable collection, like an ArrayBuffer:

def evilMutator(people: ArrayBuffer[Person]) {
    people.clear()
}

Just as with the Java code, the evilMutator method can call clear because the contents of an ArrayBuffer are mutable.

Though nobody would write malicious code like this intentionally, accidents do happen. To make your code safe from this problem, if there’s no reason for a collection to be changed, don’t use a mutable collection class. By changing the collection to a Vector, you eliminate the possibility of this problem, and the following code won’t even compile:

def evilMutator(people: Vector[Person]) {
    // ERROR - won't compile
    people.clear()
}

Because Vector is immutable, any attempt to add or remove elements will fail.

Discussion

There are at least two major benefits to using immutable variables (val) and immutable collections:

  • They represent a form of defensive coding, keeping your data from being changed accidentally.
  • They’re easier to reason about.

The examples shown in the Solution demonstrate the first benefit: if there’s no need for other code to mutate your reference or collection, don’t let them do it. Scala makes this easy.

The second benefit can be thought of in many ways, but I like to think about it when using actors and concurrency. If I’m using immutable collections, I can pass them around freely. There’s no concern that another thread will modify the collection.

Using val + mutable, and var + immutable

As mentioned several times in this chapter, it’s important to have a balanced attitude. I generally use that expression in regards to pure functions, but it also has meaning when discussing “prefer immutability.”

For instance, some developers like to use these combinations:

  • A mutable collection field declared as a val.
  • An immutable collection field declared as a var.

These approaches generally seem to be used as follows:

  • A mutable collection field declared as a val is typically made private to its class (or method).
  • An immutable collection field declared as a var in a class is more often made publicly visible, that is, it’s made available to other classes.

As an example of the first approach, the current Akka FSM class (scala.akka.actor.FSM) defines several mutable collection fields as private val fields, like this:

private val timers = mutable.Map[String, Timer]()

// some time later ...
timers -= name
timers.clear()

This is safe to do, because the timers field is private to the class, so its mutable collection isn’t shared with others.

An approach I used on a recent project is a variation of this theme:

class Pizza {
    private val _toppings = new collection.mutable.ArrayBuffer[Topping]()
    def toppings = _toppings.toList
    def addTopping(t: Topping) { _toppings += t }
    def removeTopping(t: Topping) { _toppings -= t }
}

This code defines _toppings as a mutable ArrayBuffer, but makes it a val that’s private to the Pizza class. Here’s my rationale for this approach:

  • I made _toppings an ArrayBuffer because I knew that elements (toppings) would often be added and removed.
  • I made _toppings a val because there was no need for it to ever be reassigned.
  • I made it private so its accessor wouldn’t be visible outside of my class.
  • I created the methods toppings, addTopping, and removeTopping to let other code manipulate the collection.
  • When other code calls the toppings method, I can give them an immutable copy of the toppings.

I intentionally didn’t use the “val + mutable collection” approach, which would have looked like this:

// did not do this
val toppings = new collection.mutable.ArrayBuffer[Topping]()

I didn’t use this approach because I didn’t want to expose toppings as an immutable collection outside of my Pizza class, which would have happened here, because the val would have generated an accessor method. In using an OOP design, you think, “Who should be responsible for managing the toppings on the pizza?” and Pizza clearly has the responsibility of maintaining its toppings.

I also didn’t choose this “var + immutable collection” design:

var toppings = Vector[Topping]()

The benefits of this approach are (a) it automatically shares toppings as an immutable collection, and (b) it lets me add toppings like this:

def addTopping(t: Topping) = toppings :+ t

But the approach suffers, because it’s a little cumbersome to remove an element from a Vector (you have to filter the undesired toppings out of the originating Vector while assigning the result to a new Vector), and it lets toppings be reassigned outside of the Pizza class, which I don’t want:

// bad: other code can mutate 'toppings'
pizza.toppings = Vector(Cheese)

You can remove elements with this approach by using the filter method and then reassigning the result back to toppings, like this:

toppings = toppings.filter(_ != Pepperoni)

But if you create a “double pepperoni” pizza by having two instances of Pepperoni in toppings, and then want to change it to a regular pepperoni pizza, the earlier ArrayBuffer approach is simpler.

Summary

In summary, always begin with the “prefer immutability” approach, and relax that philosophy when it makes sense for the current situation, that is, when you can properly rationalize your decision.

See Also

  • Recipe 10.6, “Understanding Mutable Variables with Immutable Collections”