Mutable Collections
On race day, the timing desk records each finisher as they cross the line, hundreds of times in a few minutes. Building a fresh immutable list for every finisher is possible, but sometimes you want one collection that changes in place. Scala has those too; you just have to ask for them by name.
The rule
Immutable is the default; mutable must be imported and chosen on purpose.
Every collection so far, List, Vector, Set and Map, has been immutable. The changeable versions live in the package scala.collection.mutable:
import scala.collection.mutable
val finishers = mutable.ArrayBuffer("Chen", "Asha")
finishers += "Ben" // ArrayBuffer(Chen, Asha, Ben)
finishers -= "Asha" // removes the first "Asha"
finishers(0) = "Chen (PB)" // replace by position
val totals = mutable.Map("Asha" -> 13.5)
totals("Ben") = 6.0 // add a key
totals("Asha") += 3.2 // update in place: now 16.7- ArrayBuffer is a growable sequence: adding and removing at the end is cheap, and reading by position is fast.
- mutable.Map and mutable.Set change in place with
+=,-=and assignment by key. - Importing the package and writing
mutable.in front of the name tells every reader which collections can change.
There are now two ways to keep a log that grows. Compare them:
var log = List(5.0, 8.5) // immutable list, reassignable name
log :+= 3.2 // log = log :+ 3.2, a new list
val buffer = mutable.ArrayBuffer(5.0, 8.5) // fixed name, changing contents
buffer += 3.2 // the same buffer, one more elementNote that log += 3.2 does not compile for a List, because a List has no +=; :+= is the form that works.
The shortcut
Use the "who can see it?" rule. If a collection is built up inside one function and then handed out, a mutable buffer inside is fine: fill it, then return buffer.toList, an immutable copy. If other parts of the program hold on to it, keep it immutable so nothing can change it underneath them.
Inside one function
Create
val finishers = mutable.ArrayBuffer[String](). Only this function can see it.
The trap: thinking a val protects a mutable collection. val only fixes which buffer the name refers to; the buffer's contents can still change.
Drills
Answer fast — trust the rule.
What must you write before using mutable.ArrayBuffer?
val buf = mutable.ArrayBuffer(5.0) then buf += 8.5. What happens?
var log = List(5.0) then log :+= 8.5. What is log now?
What is the default choice for a collection that many parts of the program read?
A function collects race finishers in a buffer. What should it return?
Remember
- Collections are immutable by default; mutable ones come from
scala.collection.mutable. - Ask "who can see it?": mutate privately, and hand out immutable snapshots with
toList. - Trap: a
valdoes not freeze a mutable collection's contents.
