clojure和^:dynamic

clojure and ^:dynamic

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

1
2
3
4
5
6
7
8
9
user=> (defn f []
           (def ^:dynamic x 5)
           (defn g [] (println x))
           (defn h [] (binding [x 3] (g)))
           (h))
#'user/f
user=> (f)    
5
nil

感到困惑,我尝试了这个简单一些的代码:

1
2
3
4
5
6
7
8
9
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

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

提示:我只是意识到以下工作原理(仍然不能完全理解原因):

1
2
3
4
5
6
7
8
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一起使用)。

例:

1
2
3
4
5
6
7
8
9
(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。

最后,您可能会发现一些样式点会有所帮助:

  • 通常将defdefn放在函数中是一个坏主意,因为它们会影响封闭的名称空间。在函数中,应改为使用(let [foo bar] ...)
  • 当您发现自己想使用binding时,通常应该考虑是否可以使用高阶函数来达到相同的结果。 binding在某些情况下很有用,但通常不是传递参数的好方法-从长远来看,函数组合通常更好。原因是binding创建执行函数所需的隐式上下文,这可能很难测试/调试。