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
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
Next, let's change the
firstName of one
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
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
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
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.
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.