what is the practical purpose of clojure's dynamic vars and binding?
Asked Answered
C

2

9

I had a look at the references: http://clojure.org/vars#Vars%20and%20the%20Global%20Environment, http://clojuredocs.org/clojure_core/clojure.core/binding

as well as clojure and ^:dynamic and Clojure Dynamic Binding

I still don't understand why there is a need for binding at all as every program I have written have been without them and I can find ways to write the examples in the conventional way - which I find more understandable. Are there examples of projects/programming paradigms that make used of this?

for example... in the animal speak example, you can get a similar effect with:

(def dog {:name "Dog" :sound "Woof"})
(def cat {:name "Cat" :sound "Meow"})

(defn speak [animal]
   (str (:name animal) " says " (:sound animal))

(println (speak dog))
(println (speak cat))

no macros, no dynamic binding... still very clean.

Champlin answered 17/10, 2012 at 10:42 Comment(1)
Stuart Sierra talks about the implications of dynamic scoping in Clojure in this blog post: stuartsierra.com/2013/03/29/perils-of-dynamic-scopeVinegary
L
16

There isn't strictly a need for them: as you rightly observe you can do anything you like without binding, and indeed if binding didn't exist then you could re-implement it relatively easily using macros and Java's ThreadLocals.

Binding is however useful as a way of passing dynamic context to a function without needing to explicitly pass a parameter.

It is particularly useful when you are composing deeply nested higher order functions and don't want to add extra parameters to every single function in the call stack in order to pass some value to the lower level functions embedded deep within.

To build on your example:

(def ^:dynamic *loud-noises* false)

(defn speak [animal]
     (str (:name animal) " says " 
          (let [sound (:sound animal)]
            (if *loud-noises* (.toUpperCase sound) sound))))

(speak dog)
=> "Dog says Woof"

(binding [*loud-noises* true]
  (speak dog))
=> "Dog says WOOF"

Note I didn't need to add an extra parameter to the speak function to get different behaviour. Adding an extra parameter would be trivial in this case, but imagine if the speak function was buried deep within a complex higher order function.....

Still, I think the best advice overall is to avoid dynamic binding unless you really need it. It is usually better to add explicit parameters if you can: direct parameters make it easier to test and reason about functions.

Liponis answered 17/10, 2012 at 12:27 Comment(2)
Why would you preference binding to adding extra parameters to every function in a deeply nested function? If my function calls (speak dog) and (close-door), how do I know that *loud-noises* corresponds to speak and not close-door?Antoineantoinetta
@Antoineantoinetta I think you highlight exactly the danger with dynamic binding and why designs that use it should be very carefully considered. Dynamic binding does allow context to be transferred across layers where you don't control the middle. For example the clojure print functions allow the output stream to be rebound even when used inside a third party function (which doesn't have a output stream parameter). This also allows setting thread specific behavior but I suspect dynamic binding is far more often detrimental than beneficial in most APIs.Persistence
C
1

Just following up on mikera's example above.. I can see why you would do it in a less expressive language but because clojure is so expressive, I would rather rewrite it... The loud-noise function can be changed slightly, again to achieve the same effect by adding additional parameters to speak...

(defn speak [animal & opts]
  (let [sound (:sound animal)
        sound (if (some #(= % :louder) opts)
                 (.toUpperCase sound) sound)]
    (str (:name animal) " says " sound)))


> (speak dog)
;;=> "Dog says Woof"
> (speak dog :louder)
;;=> "Dog says WOOF"

Is binding just a way to hack up a quick and dirty solution if you can't change the original code?

Champlin answered 18/10, 2012 at 0:36 Comment(1)
you don't always have the ability to add extra parameters - what if you are passing functions into library code for example? or if the functions are general purpose HOFs that you don't want to pollute with parameters that are only used in one special case?Liponis

© 2022 - 2024 — McMap. All rights reserved.