32

clojure.core has the macros bindings and with-redefs. Looking at the docstrings and the examples on clojuredocs.org, they seem to do something very similar. What is the difference and which one should I use in which situations?

Erik Kaplun
  • 37,128
  • 15
  • 99
  • 111
Miikka
  • 4,573
  • 34
  • 47
  • 3
    Also see this question: http://stackoverflow.com/questions/15747774/whats-the-point-of-defining-something-as-dynamic-when-you-dont-need-to-define/15748334#15748334 – Alex Nov 22 '13 at 14:52

1 Answers1

36

Clojure Vars can have thread-local bindings. binding uses these, while with-redefs actually alters the root binding (which is someting like the default value) of the var.

Another difference is that binding only works for :dynamic vars while with-redefs works for all vars.

Examples:

user=> (def ^:dynamic *a* 1)
#'user/*a*
user=> (binding [*a* 2] *a*)
2
user=> (with-redefs [*a* 2] *a*)
2
user=> (binding [*a* 2] (doto (Thread. (fn [] (println "*a* is " *a*))) (.start) (.join)))
*a* is  1
#<Thread Thread[Thread-2,5,]>
user=> (with-redefs [*a* 2] (doto (Thread. (fn [] (println "*a* is " *a*))) (.start) (.join)))
*a* is  2
#<Thread Thread[Thread-3,5,]>

You can use the (undocumented) binding-conveyor-fn to convey thread-local bindings into new threads:

user=> (binding [*a* 2] (doto (Thread. (#'clojure.core/binding-conveyor-fn (fn [] (println "*a* is " *a*)))) (.start) (.join)))
*a* is  2
#<Thread Thread[Thread-5,5,]>
opqdonut
  • 5,119
  • 22
  • 25
  • 6
    All of which is why `with-redefs` is meant for use in tests (where you may want to reach in and stub out a function), which `binding` can be useful in production code as well. – Peeja Sep 29 '14 at 14:43
  • 1
    @Peeja thanks, so in other words, `with-redefs` should never be used in multi-threaded contexts? – Erik Kaplun Jun 11 '19 at 11:47
  • @opqdonut as of 2019 june, the Vars documentation has a [Binding conveyance](https://clojure.org/reference/vars#conveyance) section which highlights `future`, `send`, `send-off` and `pmap` as having the capabilities for binding conveyance — does it mean that `binding-conveyor-fn` is no longer needed? – Erik Kaplun Jun 11 '19 at 11:50
  • 1
    @ErikKaplun, `binding-conveyor-fn` is not needed if you use the functions you mentioned. However, if you create threads with `(Thread. ...)`, you still need it. – Miikka Jan 14 '20 at 09:27