我试图理解动态变量和绑定函数,所以我尝试了这个(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=>
当我在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
创建了执行函数所需的隐式上下文,这可能很难进行测试/调试