Clojure 什么时候检查 recur 是否出现在尾部位置?

When does Clojure check if recur appears in tail position?

我试图了解 Clojure 对 recur 在非尾部位置的保护是如何工作的。

如果编写这样的代码,Clojure 会抛出异常:

(def some_var (recur))

但是如果我评估动态创建的代码呢?

(def code '(recur))
(def some_var (eval code))

如果您尝试 运行 REPL 中的这段代码,它似乎会无限循环。我预计它会抛出异常。

我的问题:

Clojure 什么时候检查 recur 是否在尾部位置?

我的第二个代码示例(在非尾位置动态执行的重复出现)的确切语义是什么?

recur 是编译器理解的特殊形式。在尾部位置以外的任何位置重复出现都是错误的(这就是语义)。

此处记录了更多详细信息:https://clojure.org/reference/special_forms#recur

CompilerException java.lang.UnsupportedOperationException: Can only recur from tail position

CompilerException 表示在编译表单时抛出异常(并因此执行检查)。在 eval 的情况下,表单在评估之前立即编译。

此外,来自 recur 的文档(强调已添加):

recur is functional and its use in tail-position is verified by the compiler.

请注意(从技术上讲),(recur)recur 出现在尾部位置的一种形式,尽管我认为很难说使用 recur 是正确的在建立递归点的形式之外(例如 fnloop)。

观察到的行为的解释(无限循环)

您的 eval 调用实际上导致编译代码,其中 recur 出现在尾部位置。

这是因为 eval 的实现方式——如果您将表单传递给 eval,这是一个 Clojure 持久集合,但它看起来不像 def 表单, 它被包装在 fn 形式中,fn 形式是实际编译的形式,然后调用结果函数。

以下是这如何适用于您的示例:

(eval '(recur))

;; does '(recur) look like a def form?
;; → no, so transform the above, in effect, to
((eval '(fn [] (recur)))

;; more precisely, before handing off `'(recur)` to lower-level
;; compilation methods, wrap it in `(fn [] …)`:
(fn [] (recur))

;; then immediately call the resulting function with no arguments

;; ultimate result: loop endlessly

如果您想了解发生这种情况的位置,请查看 clojure.lang.Compilerpublic static Object eval(Object form, boolean freshLoader) 方法 – link to the code as of Clojure 1.8.

请注意,出于同样的原因,目前(从 1.9.0-alpha14 开始)在 built-in REPL 中键入 (recur) 也会无限循环。不同的 REPL 实现可能会或可能不会以防止这种情况的方式预处理输入表单,然后再将它们交给 eval.

recur

的语义

这些与 Alex Miller link 编辑的官方文档中的解释完全相同,在他的回答和评论中。总而言之,recur 必须在建立 recur 目标的表单中使用; loopfnreify(内部方法实现)都是此类形式的示例。

语义是如何实施的

上述语义是在编译时通过使用少量动态 Var 强制执行的,编译器会在它下降到为编译传递的 top-level 形式的各种子形式时适当地绑定这些变量。如果您想详细了解控制流程,请在 Compiler.java 中搜索 NO_RECURLOOP_LABELLOOP_LOCALS 的用法。它的要点是,如果一个表单不在尾部位置,这些 Var 将被绑定到表明在编译时是这种情况的值。

ClojureScript 使用的设置可能更容易遵循,尽管它基于相同的基本思想。参见 analyzer.clj(使用 v1.9 标签稳定 link);特别是 *recur-frames*disallowing-recur.