Scala: Immutable collections of mutable data

Normally I just write about solutions, but I thought I'd take a moment today to write about something else. In this case I just wanted to note that it's possible to create an immutable List of mutable data in Scala. This scenario made me wonder, "What does 'immutable' mean?" Let's take a look.

As a first example, we'll create a Person class that has two fields, and the first field (firstName) can change:

// firstName is mutable
case class Person(var firstName: String, last: String)

Next, we'll create several Person instances:

val fred = Person("Fred", "Flintstone")
val wilma = Person("Wilma", "Flintstone")
val barney = Person("Barney", "Rubble")
val betty = Person("Betty", "Rubble")

Now we'll add those instances to a List, as shown in the Scala REPL:

scala> val peeps = List(fred, wilma, barney, betty)
peeps: List[Person] = List(Person(Fred,Flintstone), Person(Wilma,Flintstone), Person(Barney,Rubble), Person(Betty,Rubble))

So far, so good. We have an immutable collection of Person instances.

Next, let's change the firstName of one Person:

fred.firstName = "Frederick"

How does this affect our immutable List? If you're not sure, think about that for a moment, and then take a look at the result:

scala> peeps
res0: List[Person] = List(Person(Frederick,Flintstone), Person(Wilma,Flintstone), Person(Barney,Rubble), Person(Betty,Rubble))

As you can see, we were able to change the fred instance inside the immutable collection.

As I thought about this last night, I think it's important to understand what is immutable. In this example, you can't add or remove any elements from the List -- that list of elements is immutable -- but you can change mutable instances inside the List.

This may seem trivial, but it's also important. Before you go about thinking that you have an "immutable" collection, it's important to make sure you understand the data in the collection, and whether that data can be mutated.

A List of ArrayBuffer

My second example is similar to the first one, but by using different data types, it may help to get the point across more clearly.

Again we'll begin by creating two instances of data that can be mutated:

import scala.collection.mutable.ArrayBuffer

var fruits = ArrayBuffer("apple", "banana", "cherry")
val nums = ArrayBuffer(1,2,3)

Next, we'll add those to an immutable data structure, this time a Vector (just to be different):

scala> val v = Vector(fruits, nums)
v: Vector[scala.collection.mutable.ArrayBuffer[_ >: Int with String]] = 
   Vector(ArrayBuffer(apple, banana, cherry), ArrayBuffer(1, 2, 3))

Now we'll clear the collection of numbers and take another look at our Vector:

scala> nums.clear

scala> v
res1: Vector[scala.collection.mutable.ArrayBuffer[_ >: Int with String]] = 
      Vector(ArrayBuffer(apple, banana, cherry), ArrayBuffer())

For grins we'll add some data back to our collection of numbers and look again at the Vector:

nums += 4
nums += 5
nums += 6

scala> v
res2: Vector[scala.collection.mutable.ArrayBuffer[_ >: Int with String]] = 
      Vector(ArrayBuffer(apple, banana, cherry), ArrayBuffer(4, 5, 6))

Again, we have an immutable collection of mutable data.

Summary

As I thought about this situation last night, I was trying to decide what to write about it. I finally decided to just share these examples to help make sure that people understand what an "immutable data structure" really is. It's important to know that if you give one of these immutable collections to another developer and tell them, "Don't worry, it's an immutable data structure", you may be very surprised by the results.

Finally, I think it's important to note that I don't think this is a problem with Scala. I love Scala's flexibility. I just wanted to point out a potential problem for new developers, or others who might be wondering what would happen if you put mutable data structures into an immutable data structure.