In Clojure every variable is immutable. But when i use list comprehension like in the case below, the elem
variable seems to be mutable, because each time elem
is overwritten by 1, then by 2 and then by 3 or it is not?
(for [elem [1 2 3]]
elem)
Is this a point where mutability is allowed or am i missing something?
"Mutation" refers to an existing variable changing its contents. You could observe this if you had a reference to a variable, looked at it once, noting its value as X, and then later looked at the same variable again, noting its value is now Y. That isn't what's happening in a list comprehension.
First, let's talk about one thing that I hope you will agree is not mutation: calling a function multiple times with different values. Suppose we have
(defn triple [x]
(* x 3))
If we write [(triple 1) (triple 2)]
, do we say that x
has mutated? Of course not. There were two different invocations of the function triple
, each with a different value for x
, but those weren't the same variable: they were different instantiations of x
.
A list comprehension is the same thing. The body is a function, which is evaluated once for each of the inputs. It doesn't look like a function, because there's no fn
, but it really is one, both technically (it macroexpands into the body of a fn
) and philosophically (it handles inputs the same way as our triple
function above). (for [x xs] (f x))
is no different from writing (map f xs)
, which needs no mutation.
Usually when newcomers worry about mutation in Clojure, they are worried about let
, which allows you to replace existing bindings:
(let [x 1
_ (prn x)
x 2]
(prn x))
This prints 1 2
: doesn't this prove that x
has mutated? No, it doesn't: the old x
is still there, it's just shadowed so you can't refer to it anymore. You can prove this by using a function to let you refer to the old x
:
(let [x 1
f (fn [] x)
x 2]
(prn (f) x))
This still prints 1 2
even though both prints happen after x
was bound to 2. This is because f
still sees the old x
. The new x
is an unrelated variable with the same name; you might as well have called it y
and renamed all references to it.