28

I tried to understand dynamic variables and binding function so I tried this (clojure 1.3):

user=> (defn f [] 
           (def ^:dynamic x 5) 
           (defn g [] (println x)) 
           (defn h [] (binding [x 3] (g))) 
           (h))
#'user/f
user=> (f)     
5
nil

Confused, I tried this somewhat simpler code:

user=> (def ^:dynamic y 5)
#'user/y
user=> (defn g [] (println y))
#'user/g
user=> (defn h [] (binding [y 3] (g)))
#'user/h
user=> (h)
3
nil

What is the difference between the two pieces of code? Why does the second example work but the first does not?

Hint: I just realized that the following works (still don't fully understand why):

user=> (def ^:dynamic y 5)
#'user/y
user=> (defn f [] (defn g [] (println y)) (defn h [] (binding [y 3] (g))) (h))
#'user/f
user=> (f)
3
nil
user=> 
Matt Fenwick
  • 48,199
  • 22
  • 128
  • 192
Kevin
  • 24,871
  • 19
  • 102
  • 158

1 Answers1

43

I get 3 as a result (as you would expect) when I run your first example in Clojure 1.4.... have you tried this with a fresh REPL?

^:dynamic is an instruction to the Clojure compiler that a symbol (as defined with def) is intended to be dynamically rebound (with binding).

Example:

(def foo 1)
(binding [foo 2] foo)
=> IllegalStateException Can't dynamically bind non-dynamic var: ...

(def ^:dynamic bar 10)
(binding [bar 20] bar)    ;; dynamically bind bar within the scope of the binding
=> 20
bar                       ;; check underlying value of bar (outside the binding)
=> 10

Note that binding has dynamic scope within the calling thread - any functions called within the binding will see the modified value of bar (20), but any other threads will still see the unchanged root value of 10.

Finally a couple of style points that you may find helpful:

  • It's generally considered bad idea to put def and defn within functions as they affect the enclosing namespace. Within functions you should use (let [foo bar] ...) instead.
  • When you find yourself wanting to use binding you should normally consider whether you can achieve the same result using higher order functions instead. binding is useful in some contexts but it is not in general a good way to pass parameters around - function composition is usually better in the long run. The reason for this is that binding creates an implicit context that is required for the execution of your function and this can be difficult to test/debug.
ChrisGPT was on strike
  • 127,765
  • 105
  • 273
  • 257
mikera
  • 105,238
  • 25
  • 256
  • 415
  • I understand pros/cons of binding. I also realize the first code example is unusual clojure code. What I didn't understand is why it didn't work (with 1.3, fresh repl). – Kevin Jul 31 '12 at 04:11
  • I'm having trouble seeing when you'd ever want binding! It looks anathema to the functional way. What am I missing ? – Hendekagon Jul 31 '12 at 06:05
  • 2
    @Hendekagon - probably deserves a SO question in its own right. But I have found it useful as an extra way of passing round context while debugging / working at the REPL - if you did this in a purely functional way then you would need to thread new parameters all the way through a (potentially very long) call graph. – mikera Jul 31 '12 at 06:44
  • I tried this in 1.4, and it works (as expected), and retried it in 1.3 and it fails. I can only conclude there was some bug in 1.3 that's been since fixed. – Kevin Jul 31 '12 at 15:05
  • 1
    @Kevin `^:dynamic` was not yet part of the language as of 1.3 -- so no, not a bug, just not something that existed yet; at that time, dynamic vars were named with `*barbells*` as more than just convention. – Charles Duffy Oct 17 '12 at 13:02
  • An example where it comes in handy: to pass in a printer function to a debugging library. http://stackoverflow.com/questions/24951894/clojure-how-to-let-the-library-users-choose-which-printing-function-to-use-to/24955447#24955447 – mascip Jul 28 '14 at 09:13
  • 1
    Sorry to necromance this thread, but binding is really useful in creating a sandbox in your code by only binding the variables in certain contexts. I have used it to allow data modifications in testing/development environments, but denied the behavior in production environments. Also, If you look at the construction of the Scheme compiler in SICP, you will see that environment is passed with every (most) function evaluations. In the same way binding can be used for code isolation, data management, or used in a continuation-like manner. – Travis Rodman Apr 28 '15 at 21:50