When teaching programming, something that tends to come up a lot is when and how values can change. While there is a lot of jargon (values, references, mutability) to make communicating with people that already understand these ideas easier, it can be difficult to explain to people that don’t already understand the ideas.
The below was initially written to explain to a co-worker learning Clojure what we mean when we say “values are immutable” and I thought maybe someone else could find it useful.
In Clojure, values are immutable by default.
If you have
(let [foo [1 2 3]] ...) you can’t change that list.
You can make
foo refer to a new value, but you can’t change the value itself.
For example, if you have
(let [foo [1 2 3] bar foo] (conj foo 4)) then the values of
bar are still the same,
[1 2 3] - the
conj function returns a new vector (that we haven’t given a name to).
Even if you do something like
(let [foo [1 2 3] bar foo foo (conj foo 4)] ...)
foo now refers to the vector
[1 2 3 4], because we’ve “shadowed” what
foo refers to, but
bar is still
[1 2 3].
In languages that have mutable values, for example Python, you could do:
foo = [1, 2, 3] bar = foo foo.append(4)
foo would be
[1,2,3,4] and so would
bar, because the list is mutable, so you can actually change the underlying value the names refer to.
In Clojure, even with atoms, only the atom itself is mutable, so, for example:
(def foo (atom [1 2 3])) (def bar @foo) (swap! foo conj 4)
Now foo is an atom,
[1 2 3 4] but bar is still
[1 2 3]
This makes things much less confusing & reduces the potential for bugs, because values aren’t changing out from under you.
In the python example, imagine
foo is getting passed to some other function, then that could make the value of your
bar variable change!