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