28

我试图理解动态变量和绑定函数,所以我尝试了这个(clojure 1.3):

user=> (defn f [] 
           (def ^:dynamic x 5) 
           (defn g [] (println x)) 
           (defn h [] (binding [x 3] (g))) 
           (h))
#'user/f
user=> (f)     
5
nil

很困惑,我尝试了这个更简单的代码:

user=> (def ^:dynamic y 5)
#'user/y
user=> (defn g [] (println y))
#'user/g
user=> (defn h [] (binding [y 3] (g)))
#'user/h
user=> (h)
3
nil

两段代码有什么区别?为什么第二个例子有效,而第一个无效?

提示:我刚刚意识到以下工作(仍然不完全理解为什么):

user=> (def ^:dynamic y 5)
#'user/y
user=> (defn f [] (defn g [] (println y)) (defn h [] (binding [y 3] (g))) (h))
#'user/f
user=> (f)
3
nil
user=> 
4

1 回答 1

37

当我在 Clojure 1.4 中运行你的第一个示例时,我得到 3 结果(如你所料)......你用新的 REPL 试过了吗?

^:dynamic是对 Clojure 编译器的一条指令,指示符号(用 定义def)旨在动态反弹(用binding)。

例子:

(def foo 1)
(binding [foo 2] foo)
=> IllegalStateException Can't dynamically bind non-dynamic var: ...

(def ^:dynamic bar 10)
(binding [bar 20] bar)    ;; dynamically bind bar within the scope of the binding
=> 20
bar                       ;; check underlying value of bar (outside the binding)
=> 10

请注意,binding在调用线程内具有动态范围 - 在绑定内调用的任何函数都将看到bar(20) 的修改值,但任何其他线程仍将看到未更改的根值 10。

最后,您可能会发现一些有用的风格点:

  • 通常认为将函数放在函数内是个坏主意,def因为defn它们会影响封闭的命名空间。在你应该使用的函数(let [foo bar] ...)中。
  • 当您发现自己想要使用binding时,您通常应该考虑是否可以使用高阶函数来实现相同的结果。binding在某些情况下很有用,但通常不是传递参数的好方法 - 从长远来看,函数组合通常更好。这样做的原因是binding创建了执行函数所需的隐式上下文,这可能难以测试/调试。
于 2012-07-31T02:12:58.043 回答