So, I think this is an interesting idea, but it gets complex fast.
Consider these two different kinds of mutation that might occur in your program:
var x: Map[Int, Int] = Map.empty
x.updated(2, 3) // we've mutated the variable m.
// however, the previous and current *values* are both immutable.
import java.util.HashMap
val y: HashMap[Int, Int] = new HashMap
y.put(2, 3) // we've mutable the object y references.
// y is an immutable reference to a mutable object.
You’re quite correct that forbidding the first type of mutation is relatively straightforward: forbid var
. However, the second type of mutation (nested mutation coming from an inner object) is more difficult to detect and prevent.
Furthermore, consider the following:
final class IArray[A] private (array: Array[A]) {
def length: Int = array.length
def apply(i: Int): A = array(i)
}
object IArray {
def wrap[A](arr: Array[A]): IArray[A] =
new IArray(arr.clone)
}
Is IArray
immutable? It has a reference to an array, which can be mutated. However, it does not expose this array or mutate it itself. Nor does it store an array reference which could be mutated by an outside third-party. So it seems say to say that IArray
is immutable in the sense that (outside of reflection) it can’t be mutated, and doesn’t reference anything which could be mutated by an outsider (assuming that A
is also immutable).
Would you want to allow or forbid IArray
to be eligible for this enforced immutability?
Forbidding IArray
is tough but fair. However, many collections we currently consider immutable (e.g. List
or Vector
) have these kinds of issues (vectors use internal arrays, and both have hidden var
fields which are only intended to be used internally in hidden ways). Would lazy val
qualify? The subset of the Scala ecosystem that satisfies this is (potentially) low.
On the other hand, allowing IArray
opens up a can of worms: we’d need a way to audit internal uses of potentially-mutable references of objects to ensure that we don’t expect them to be observable. This is probably intractable in at least some cases and would need to be backstopped by explicit user-annotations of what to consider safe or unsafe.
Anyway, I hope these examples illustrate why the issue is a bit thornier than it might seem.