2024 Clojure 状况调查中分享您的想法!

欢迎!请查看有关页面获取更多关于如何工作的信息。

0
编译器
重新标记

我明白下面这个用法有点不寻常,但在我尝试将变量改为动态的时,我发现了一些奇怪的行为差异。如果尝试完全不工作,我都不会感到惊讶,但事实上它并不一致,这让我感到惊讶。

(def v :init)

(do
  (.setDynamic #'v true)
  (binding [v :new]
    (println v))
  (.setDynamic #'v false))
=> :new ; the value is changed as expected

(let []
  (.setDynamic #'v true)
  (binding [v :new]
    (println v))
  (.setDynamic #'v false))
=> :init ; the code is the same as above except for let

在深入编译器后,似乎在 ObjExpr.emitVarValue 中,它在 let 示例中认为 v 不是动态的,因此返回了根值。

有趣的是,如果我用类似 (println @#'v) 的方式撤销引用该变量,这两种情况都返回了 :new 绑定。

2 答案

+3

被选择
 
最佳答案

这就是 let 和 do 之间的区别:对于顶层 do 表达式,其体被提升并逐个编译和执行。

所以对于

(do
  (.setDynamic #'v true) ;1
  (binding [v :new] ;2
    (println v))
  (.setDynamic #'v false) ; 3
 )

表达式被分解成三个表达式

(.setDynamic #'v true) ; 1

.

(binding [v :new] ; 2
    (println v))

.

(.setDynamic #'v false) ;3

每个表达式随后都会依次编译和执行。这意味着在编译表达式 #2 之前,表达式 #1 执行的效果是可见的。

变量的动态检查发生在编译时,而不是运行时,所以表达式 #1 运行意味着表达式 #2 中对 v 的引用将以不同的方式编译来处理可能被 rebound 的变量。

对于 let 表达式,整个表达式都会一次性编译然后执行。因此,当 #2 被编译时,#1 还未执行,所以变量不会被标记为动态的,因此编译器不会发出处理 bound 变量的代码。

+2

Var.setDynamic() 不是 Clojure 的公共 API 的一部分,所以我不能确定这个问题是否相关。

是的,我猜到是这个。

我在尝试使用 `binding` 而不是最初声明变量为动态的情况下(因为我只想在测试时更改函数,而不是在实际代码中),遇到了 .setDynamic(),所以我想试一试。
...