14

Trying to learn Clojure and you can't help but be told continually how Clojure is all about immutable data. But you can easily redefine a variable by using def right? I get that Clojure developers avoid this but you could avoid changing variables in any language just the same. Can someone explain to me how this is any different, because I think I'm missing that from the tutorials and books I'm reading.

To give an example how is

a = 1
a = 2

in Ruby (or blub, if you prefer) different from

(def a 1)
(def a 2)

in Clojure?

3 Answers 3

12

As you already noticed, the fact that mutability is discouraged in Clojure does not mean that it is forbidden and that there are no constructs that support it. So you are right that using def you can change / mutate a binding in the environment in way similar to what assignment does in other languages (see the Clojure documentation on vars). By changing bindings in the global environment you also change data objects that use these bindings. For example:

user=> (def x 1)
#'user/x
user=> (defn f [y] (+ x y))
#'user/f
user=> (f 1)
2
user=> (def x 100)
#'user/x
user=> (f 1)
101

Notice that after redefining the binding for x, function f has changed as well, because its body uses that binding.

Compare this with languages in which redefining a variable does not delete the old binding but only shadows it, i.e. it makes it invisible in the scope that comes after the new definition. See what happens if you write the same code in the SML REPL:

- val x = 1;
val x = 1 : int
- fun f y = x + y;
val f = fn : int -> int
- f 1;
val it = 2 : int
- val x = 100;
val x = 100 : int
- f 1;
val it = 2 : int

Notice that after the second definition of x, the function f still uses the binding x = 1 that was in scope when it was defined, i.e. the binding val x = 100 does not overwrite the previous binding val x = 1.

Bottomline: Clojure allows to mutate the global environment and redefine bindings in it. It would be possible to avoid this, as other languages like SML do, but the def construct in Clojure is meant to access and mutate a global environment. In practice, this is very similar to what assignment can do in imperative languages like Java, C++, Python.

Still, Clojure provides lots of constructs and libraries that avoid mutation, and you can come a long way without using it at all. Avoiding mutation is by far the preferred programming style in Clojure.

1
  • 1
    Avoiding mutation is by far the preferred programming style. I'd suggest that this statement applies to every language these days; not just Clojure ;)
    – David Arno
    Commented Jan 8, 2016 at 21:27
3

Clojure is all about immutable data

Clojure is about managing mutable state by controlling the mutation points (i.e., Refs, Atoms, Agents, and Vars). Though, of course, any Java code you use via interop can do as it pleases.

But you can easily redefine a variable by using def right?

If you mean bind a Var (as opposed to, e.g., a local variable) to a different value, then yes. In fact, as noted in Vars and the Global Environment, Vars are specifically included as one of Clojure's four "reference types" (though I'd say they are mainly referring to dynamic Vars there).

With Lisps, there is a long history of performing interactive, exploratory programming activities via the REPL. This often involves defining new variables and functions, as well as redefining old ones. However, outside of the REPL, re-defing a Var is considered poor form.

2

From Clojure for the Brave and True

For example, in Ruby you might perform multiple assignments to a variable to build up its value:

severity = :mild
  error_message = "OH GOD! IT'S A DISASTER! WE'RE "
  if severity == :mild
    error_message = error_message + "MILDLY INCONVENIENCED!"
  else
    error_message = error_message + "DOOOOOOOMED!"
  end

You might be tempted to do something similar in Clojure:

(def severity :mild)
  (def error-message "OH GOD! IT'S A DISASTER! WE'RE ")
  (if (= severity :mild)
      (def error-message (str error-message "MILDLY INCONVENIENCED!"))
  (def error-message (str error-message "DOOOOOOOMED!")))

However, changing the value associated with a name like this can make it harder to understand your program’s behavior because it’s more difficult to know which value is associated with a name or why that value might have changed. Clojure has a set of tools for dealing with change, which you’ll learn about in Chapter 10. As you learn Clojure, you’ll find that you’ll rarely need to alter a name/value association. Here’s one way you could write the preceding code:

(defn error-message [severity]
   (str "OH GOD! IT'S A DISASTER! WE'RE "
   (if (= severity :mild)
     "MILDLY INCONVENIENCED!"
     "DOOOOOOOMED!")))

(error-message :mild)
  ; => "OH GOD! IT'S A DISASTER! WE'RE MILDLY INCONVENIENCED!"
2
  • Couldn't one easily do the same thing in Ruby? The suggestion given is simply to define a function that returns a value. Ruby has functions too!
    – Evan Zamir
    Commented Apr 22, 2016 at 22:38
  • Yes, I know. But instead of encouraging an imperative way of solving the proposed problem (like changing bindings), Clojure adopt the funcional paradigm. Commented Apr 23, 2016 at 0:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.