【问题标题】:Difference between using "def" to update a var and "alter-var-root"使用“def”更新 var 和“alter-var-root”之间的区别
【发布时间】:2013-05-08 18:24:11
【问题描述】:

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

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

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

【问题讨论】:

  • 两个答案都非常有启发性,谢谢!我希望我能接受他们两个。

标签: clojure


【解决方案1】:

我发现 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>

最后一个用例是我在实践中唯一需要的。例如,强制clojure.logging 使用正确的 slf4j 记录器作为 Pallet 项目的示例:

(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)))))

这只是使用alter-var-root 来重置另一个命名空间中的 var,而不管它在初始化时的内容如何。我想这有点骇人听闻...

【讨论】:

  • 但是多次定义 var 被认为是不好的风格,因为 def 总是定义顶级 var。如果您想更改某些 var 的绑定,正确的做法是使用 alter-var-root
  • 作为一个新手,我觉得这个答案解释了什么时候可以使用 alter-var-root,而不是为什么你会使用它。有哪些常见用例?
  • 也许另一个用例是设置应用程序处理程序的值,因此当您在 REPL 上进行开发时,您不必在更改传递给的处理程序时停止应用程序服务器一个创建服务器(我认为这很容易找到参考,但很抱歉,因为我的 atm 太短了)。
  • @RodrigoTaboada: 对于已经存在的 Var 对象,使用其符号名称调用def不会创建其他对象,但会更新现有对象中的引用一。
  • alter-var-root 可用于处理运行时发生的状态初始化。常见的惯用用法示例可以在 Stuart Sierra 的 component 库中找到。它不是一个 hack,尽管可以使用 atom 代替。如果值在运行时初始化一次且仅一次,则使用 var 和 alter-var-root 的语义可能比使用 atom 更清晰。但我想 alter-var-root 对于 clojure 初学者来说会更具挑战性。
【解决方案2】:

alter-var-root 为函数应用程序提供了原子化的附加值。 (alter-var-root #'foo inc) 的两个(可能并发)应用程序保证foo 将增加2。

(def x (inc x)) 没有这样的保证。它可能会覆盖其他线程在读取x 的值和写入其更新值之间所做的任何更改。

另一方面,如果您使用 alter-var-root 的原子性,那么对于您的用例来说,原子可能比 vars 更好。

【讨论】:

  • 这两种构造都没有原子性保证。
  • 或者我在考虑 with-redefs...有保证的链接吗?
  • @amalloy alter-var-root 代表clojure.lang.Var.alterRoot,即synchronizedbindRoot 也是 synchronized,但它当然将新的根值作为参数,因此在尝试获取锁之前必须准备好。
  • @amalloy:我记得 alter-var-root 上的文档字符串说明了有关“原子应用”更改的内容。当我使用合适的浏览器时会尝试重新检查。
  • 这里是1.5.1源码的相关片段:clojure.core/alter-var-rootclojure.lang.Var.alterRoot。编辑:文档字符串也确实提到了这个保证——“原子地改变......”——根据 Rafał 的评论(因为我现在使用的是正确的浏览器;-))。
【解决方案3】:

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]

【讨论】:

    猜你喜欢
    • 2017-01-21
    • 1970-01-01
    • 1970-01-01
    • 2013-02-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多