gpt4 book ai didi

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

转载 作者:太空宇宙 更新时间:2023-11-03 18:46:05 24 4
gpt4 key购买 nike

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

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

(def some_var (recur))

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

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

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

我的问题:

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

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

最佳答案

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

您的 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 开始)在内置 REPL 中键入 (recur) 也会无限循环。不同的 REPL 实现可能会或可能不会以防止这种情况的方式预处理输入表单,然后再将它们交给 eval

recur 的语义

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

如何执行语义

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

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

关于recursion - Clojure 什么时候检查 recur 是否出现在尾部位置?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41666786/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com