Heiko's Blog

About programming and other fun things

We Are Reactive

Attention: Seq Is Not Immutable!

| Comments

Update: Thanks to gerferra (see comment below) I have added a paragraph explaining that you should use chained package clauses in combination with retargeting Seq.

One of Scala’s guiding principles is its bias towards immutability: While we can use vars and mutable objects, we are encouraged to use vals and immutable objects. This manifests itself very clearly in the collection library which contains both immutable and mutable collections. Actually there are three main packages:

  1. scala.collection
  2. scala.collection.immutable
  3. scala.collection.mutable

scala.collection contains basic objects which are extended by the immutable collections in scala.collection.immutable and the mutable ones in scala.collection.mutable.

It is important to understand that the basic collections are neither immutable or mutable, they just provide common functionality. If an API makes use of a basic collection, e.g. for the type of a method parameter, we can either use an immutable or mutable one for the argument.

Let’s look at a simple example:

1
2
def basicSetSize[A](as: scala.collection.Set[A]): Int =
  as.size
1
2
3
4
5
scala> basicSetSize(scala.collection.immutable.Set(1, 2, 3))
res0: Int = 3

scala> basicSetSize(scala.collection.mutable.Set(1, 2, 3))
res1: Int = 3

As you can see, we can use an immutable or a mutable Set for a basic Set. So far so good.

Now back to Scala’s guiding principle of immutability. In order to give preference to the immutable collections, the Predef singleton object and the scala package object contain a number of aliases that bring the immutable collections into scope without any imports. That means that we can use many immutable collections even if we don’t import anything from either scala.collection.immutable or scala.collection.mutable.

Let’s rewrite the above example:

1
2
def basicSetSize[A](as: Set[A]): Int =
  as.size
1
2
3
4
5
6
7
8
9
10
11
12
scala> basicSetSize(scala.collection.immutable.Set(1, 2, 3))
res0: Int = 3

scala> basicSetSize(scala.collection.mutable.Set(1, 2, 3))
<console>:9: error: type mismatch;
 found   : scala.collection.mutable.Set[Int]
 required: scala.collection.immutable.Set[?]

scala> basicSetSize(scala.collection.Set(1, 2, 3))
<console>:9: error: type mismatch;
 found   : scala.collection.Set[Int]
 required: scala.collection.immutable.Set[?]

As you can see, Set without imports or qualifying package essentially means scala.collection.immutable.Set and we can’t use a mutable or basic Set.

Now this is expected, because Scala is encouraging us to use immutable objects. Many think that this principle holds for all major collections types, but this is not true! Guess where the aliases for Seq are pointing to: To scala.collection.immutable.Seq like for Set and other collections? No!

1
2
type Seq[+A] = scala.collection.Seq[A]
val Seq = scala.collection.Seq

These aliases are defined in the scala package object. The reason for this exception is, that one should be able to use arrays, which are mutable, for the “default” sequence. Predef contains implicit conversions from Array to WrappedArray which mixes in various mutable collection traits.

While this makes it comfortable to work with arrays, I consider this very dangerous, because it is too easy to forget importing scala.collection.immutable.Seq. If we forget this import and hence use the basic sequence in our API, users can provide mutable sequences which most certainly will lead to trouble in any concurrent program. Just imagine objects used as messages in an Akka based system …

If you want to be sure your code is using immutable sequences, I recommend adding the following lines to the top-level package object in your projects:

1
2
3
type Seq[+A] = scala.collection.immutable.Seq[A]

val Seq = scala.collection.immutable.Seq

If you use chained package clauses (see below) in all your Scala source file, this will automatically “retarget” Seq to the immutable sequence and all should be good.

1
2
package name.heikoseeberger.toplevel
package subpackage

Comments