Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference between using "def" to update a var and "alter-var-root"

Tags:

clojure

What's the difference between using "def" to update a var and using "alter-var-root"? e.g.

(def x 3)
(def x (inc x))

vs

(def x 3)
(alter-var-root #'x inc)
like image 205
Anonymous Avatar asked May 08 '13 18:05

Anonymous


3 Answers

I find alter-var-root very rarely comes up in idiomatic Clojure code; not that there is anything wrong with it, it's just intended for corner cases. If you find yourself using it to build loops and such it's a sign something needs a different approach. I mostly see it in initialization routines for setting access credentials or loggers and such.

alter-var-root uses a function to mechanically change the value of a var while def just sets it to a new value. In your example they are equivalent.

hello.exp> (def foo 4)
#'hello.exp/foo
hello.exp> (alter-var-root #'foo inc)
5
hello.exp> foo
5

alter-var-root is also unwilling to create a new var:

hello.exp> (alter-var-root #'foo1 inc) 
CompilerException java.lang.RuntimeException: Unable to resolve var: foo1 in this context, compiling:(NO_SOURCE_PATH:1) 

alter-var-root can work on other namespaces as well:

hello.exp> (in-ns 'user)
#<Namespace user> 
user> (alter-var-root #'hello.exp/foo inc) 
 6
user> (def hello.exp/foo 4)
CompilerException java.lang.RuntimeException: Can't create defs outside of current ns, compiling:(NO_SOURCE_PATH:1)
user>

This last use case is the only one I have ever needed in practice. For instance forcing clojure.logging to use the correct slf4j logger as an example from the Pallet project:

(defn force-slf4j
  "The repl task brings in commons-logging, which messes up our logging
   configuration. This is an attempt to restore sanity."
   []
  (binding [*ns* (the-ns 'clojure.tools.logging.slf4j)]
    (alter-var-root
     #'clojure.tools.logging/*logger-factory*
     (constantly (clojure.tools.logging.slf4j/load-factory)))))

Which is just using alter-var-root to reset a var in another namespace regardless of its content on initialization. I suppose it's a bit of a hack ...

like image 186
Arthur Ulfeldt Avatar answered Nov 18 '22 20:11

Arthur Ulfeldt


alter-var-root provides the added value of being atomic with regards to the function application. Two (possibly concurrent) applications of (alter-var-root #'foo inc) guarantee that foo will increase by 2.

With (def x (inc x)) there is no such guarantee. It might overwrite any changes done by other threads between reading the value of x and writing its updated value.

On the other hand, if you are using alter-var-root for its atomicity then perhaps atoms are better for your use case than vars.

like image 27
Rafał Dowgird Avatar answered Nov 18 '22 21:11

Rafał Dowgird


With def:

(def w (vector))        ; create Var named w and bind it to an empty vector
(dotimes [x 9]          ; repeat 9 times (keeping iteration number in x):
 (future                ;  execute in other thread:
  (def w                ;   replace root binding of w with
    (conj w             ;    a new vector with all elements from previous (w)
          x))))         ;     with added an element indicating current iteration (x) 

w                       ; get a value of Var's root binding (identified by symbol w)

; => [0 2 3 6 8 7 4 5]  ; 1 is missing !!!
                        ; second thread overlapped with another thread
                        ; during read-conjoin-update and the other thread "won"

With alter-var-root:

(def w (vector))        ; create Var named w and bind it to an empty vector
(dotimes [x 9]          ; repeat 9 times (keeping iteration number in x):
 (future                ;  execute in other thread:
  (alter-var-root #'w   ;   atomically alter root binding of w
   (fn [old]            ;    by applying the result of a function,
    (conj               ;     that returns a new vector
     old                ;      containing all elements from previous (w)
     x)))))             ;      with added an element indicating current iteration (x) 

w                       ; get a value of Var's root binding (identified by symbol w)

; => [1 2 4 5 3 0 7 8 6]
like image 15
siefca Avatar answered Nov 18 '22 20:11

siefca