3

I always load a namespace not-really-constants. It contains, among other definitions:

(def foo 0.05)

Another namespace contains

(ns other-ns
   (require [not-really-constants :as cn]))

(defn bar [x] (* x cn/foo))

However, sometimes I want foo to have a different value. So I load a namespace containing

(in-ns 'not-really-constants)
(def foo 0.025)

And then I call other-ns/bar in the place where it would normally be called. bar then uses the new value of foo, 0.025, rather than the original 0.05.

I'm not sure whether to be suprised about this behavior. I think of redefining using def as a handy convenience in the repl, but as something that's one is not supposed to do in normal code. However, I'm not sure why. Redefining foo in this way seems to work without trouble running everything from the commandline using lein run, loading files via require.

So, my question is: What are the dangers or other drawbacks of this practice--i.e. of redefining a variable using def?

(BTW I don't add ^:dynamic to the definition of foo because I don't need thread-local bindings; the new value of foo should be used everywhere. If I add ^:const to the definition of foo, bar uses the original 0.05 value despite the fact that I redefined foo, as bar should in that case.)

Mars
  • 8,689
  • 2
  • 42
  • 70

2 Answers2

3

Redefining a var is not thread safe and should not be done in production environments. There is a good response available to a similar question that goes into more depth on the underlying reasons.

Community
  • 1
  • 1
Symfrog
  • 3,398
  • 1
  • 17
  • 13
  • Thanks Symfrog. The answers by Arthur Ulfeldt and mikera to that question are excellent, and characteristically illuminating. (I wouldn't have found that question, given how I was searching.) That question is about function definitions, but seems to apply to any var, and resolves my worry. I understand now why what I'm doing should not lead to problems: I only redefine a var during the initialization stage of the program, before any additional threads are created. I know now what to avoid. – Mars Dec 22 '14 at 17:59
1

The main drawback to redefining vars in Clojure has less to do with thread safety, and more to do with the inherent dangers of mutable state. Idiomatic Clojure strives to be purely functional and free of side-effects. This makes it much easier to reason about the logic of your program - given the same inputs, a function will always produce the same output.

When you redefine a var, you're basically treating it as a mutable global variable. The behavior of code that uses that variable now depends not only on its inputs, but the order in which it is executed relative to other code that might have altered that variable. This extra dimension of time makes it much harder to reason about your program.

I'd treat redefining a var as a code smell: it might be OK in this particular situation, but should be an indication that you should re-evaluate your approach and see if there's a better way.

Alex
  • 13,811
  • 1
  • 37
  • 50
  • Thanks Alex. I took this point for granted, but you were right to make it explicit. I had thought about ways to avoid redefining, but they weren't worth the trouble for a change that only happens during initialization. So, like Symfrog's answer, your answer increases my confidence that my strategy is safe! (The rest of my program is purely functional except in a few carefully constrained contexts where efficiency requires mutating temporary data structures inside functions.) – Mars Dec 26 '14 at 06:15