clojure和^:动态

Kev*_*vin 26 clojure dynamic-scope

我试着理解动态变量和绑定函数,所以我尝试了这个(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
Run Code Online (Sandbox Code Playgroud)

困惑,我尝试了这个稍微简单的代码:

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
Run Code Online (Sandbox Code Playgroud)

这两段代码有什么区别?为什么第二个例子有效但第一个没有?

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

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=> 
Run Code Online (Sandbox Code Playgroud)

mik*_*era 32

当我在Clojure 1.4中运行你的第一个例子时,我得到3(正如你所期望的那样)....你用新鲜的REPL试过这个吗?

^:dynamic是Clojure编译器的一个指令,一个符号(定义为def)旨在动态反弹(with 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
Run Code Online (Sandbox Code Playgroud)

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

最后你会发现一些有用的风格点:

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

  • @Hendekagon-可能值得单独提出一个SO问题。但是我发现它作为在REPL上调试/工作时传递上下文的一种额外方法很有用-如果您以纯功能的方式进行此操作,则您将需要通过(可能很长的)调用一直对新参数进行线程化图形。 (2认同)