Immutability
BasictheoryIn Clojure, the built-in collections are persistent: every "update" returns a brand new value and leaves the original untouched. You don't mutate data; you derive new versions of it. That sounds expensive, but Clojure shares structure between versions, so the cost is small and the guarantees are huge.
Minimal example
conjclojure.core/conjAdd an element to a collection (vector: append, list: prepend).view on clojuredocs → returns a new vector with one more element. The original is unchanged when you look at it again:
The same holds for maps. assocclojure.core/assocReturn a new map with k -> v associated.view on clojuredocs → produces a new map; the original still has the old value:
Practical example
Because values do not change behind your back, you can hold onto a snapshot and trust it. Here a "transaction history" keeps every prior state:
This is why local reasoning works in Clojure: if you bound xs ten lines ago,
nothing your callees do can make xs mean something else now.
Immutability is what makes functions pure
There's a benefit of immutability that often goes unsaid: it's what lets a function be pure, and purity is what lets functions compose. Two related ideas are worth keeping distinct:
- A pure function returns the same output for the same input. A function that throws is still pure, as long as it throws for the same inputs — purity is about determinism, not about never failing.
- Referential transparency is stronger: an expression can be replaced by its
result without changing the program's meaning. If
(f a)evaluates tob, then(g (f a))and(g b)are interchangeable.
Referential transparency requires immutability — (f a) can only stand in for
its result if it produced no side effect and returned a value that won't shift
underneath g. And that substitutability is precisely what
composition leans on: g builds on f's
result without caring how it was produced. See also
Purity vs side effects.
Exercise
Define rename-key, which takes a map m, an old key k, and a new key k',
and returns a new map with the value moved from k to k'. The original m
must not change.