30

使用“def”更新 var 和使用“alter-var-root”有什么区别?例如

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

对比

(def x 3)
(alter-var-root #'x inc)
4

3 回答 3

30

我发现 alter-var-root 很少出现在惯用的 Clojure 代码中。并不是说它有什么问题,它只是用于极端情况。如果您发现自己使用它来构建循环,那么这表明某些事情需要不同的方法。我主要在用于设置访问凭据或记录器等的初始化例程中看到它。

alter-var-root使用函数来机械地更改 var 的值,同时def将其设置为新值。在您的示例中,它们是等效的。

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

alter-var-root也不愿意创建一个新的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也可以在其他命名空间上工作:

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 ...

于 2013-05-08T18:53:55.430 回答
22

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.

于 2013-05-09T08:23:28.997 回答
15

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"

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]
于 2014-12-06T20:49:30.240 回答