le_*_*_me 2 lisp recursion tail-recursion clojure
我试图了解Clojure对recur非尾部位置的保护是如何起作用的.
如果编写类似的代码,Clojure会抛出异常:
(def some_var (recur))
Run Code Online (Sandbox Code Playgroud)
但是,如果我评估动态创建的代码呢?
(def code '(recur))
(def some_var (eval code))
Run Code Online (Sandbox Code Playgroud)
如果您尝试在REPL中运行此代码,它似乎无限循环.我预计它会抛出异常.
我的问题:
当Clojure确实检查复发是否处于尾部位置时?
我的第二个代码示例的确切语义是什么(动态执行的非尾部位置的重复)?
您的eval调用实际上会导致编译尾部位置recur 确实发生的代码.
这是因为如何eval实现 - 如果你传递一个evalClojure持久集合的表单,但它看起来不像一个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
Run Code Online (Sandbox Code Playgroud)
如果你想看看这发生了什么,请看一下public static Object eval(Object form, boolean freshLoader)方法clojure.lang.Compiler- 链接到Clojure 1.8的代码.
请注意,出于同样的原因,当前输入(recur)内置REPL(从1.9.0-alpha14开始)也会无休止地循环.不同的REPL实现可能会或可能不会预先处理输入表单,以防止这种情况发生之前eval.
recur这些与Alex Miller在他的回答和评论中所关联的官方文档中所解释的完全相同.总而言之,recur必须在建立recur目标的表格内使用; loop,fn和reify(方法实现内部)是这样的形式的所有实施例.
上面的语义是在编译时通过使用少量动态Vars来强制执行的,当编译器下载到为编译传递的顶级表单的各种子表单时,编译器会对其进行适当的绑定.如果你想跟着详细的控制流程,搜索的用途NO_RECUR,LOOP_LABEL并LOOP_LOCALS在Compiler.java.它的要点是,如果表单不在尾部位置,这些Vars将绑定到表示编译时就是这种情况的值.
尽管ClojureScript基于相同的基本思想,但它使用的设置可能更容易理解.请参阅analyzer.clj(使用v1.9标记的稳定链接); 特别是*recur-frames*和disallowing-recur.