从Clojure/dev Google组的提案邮件复制
https://groups.google.com/d/msg/clojure-dev/zlMGmv60MVA/beyIRTrhAgAJ
你好,
目前loop/recur仅限于"单层"循环:loop形式可以放在其他loop形式内部,但没有提供"递归到外部循环"的设施。
几年前,我提出了一项提案,建议为Clojure添加对嵌套循环的支持,并附带了ClojureScript的proof-of-concept补丁,使用了我认为足以使嵌套循环感觉自然,同时仍然自然扩展核心loop/recur模型的语法和语义,具有相同的显式尾递归优势
(loop foo [...] ; 命名循环
...
(loop [...] ; 干预循环
...
(loop [...] 内层循环
...
(recur-to foo ...)))) ; 回到命名循环
NB. 这必须在尾递归位置
关于所有三个循环
https://groups.google.com/d/msg/clojure-dev/imtbY1uqpIc/8DWLw8Ymf4IJ
https://dev.clojure.org/display/design/Named+loops+with+recur-to
https://github.com/michalmarczyk/clojurescript/tree/recur-to
https://github.com/michalmarczyk/clojurescript/commit/feba0a078138da08d584a67e671415fc403fa093
我现在已经实施了一个完整的补丁,该补丁在Clojure中实现了提议的功能(第一个链接是基于当前master的分支,即1.9.0-alpha17之后的"准备下一个开发迭代"提交;第二个链接是该分支的当前最新代码,供未来参考)
https://github.com/michalmarczyk/clojure/tree/recur-to
https://github.com/michalmarczyk/clojure/commit/212ea06d21d3b336ac35480c99170e81defaf956
我还打开了JIRA中的一个票证,以便将上述内容以补丁文件的形式发布
https://dev.clojure.org/jira/browse/CLJ-2235
此邮件的其余部分更详细地概述了提案,以一种相当严谨的形式说明了其关键特性,简要总结了实现方法,并讨论了补丁中做出的某些设计选择。
概述
========
想法是,可以编写例如:
(let [m (two-dimensional-matrix)]
(loop iloop [i 0] 命名循环
(if (< i i-lim)
(loop [j 0] 嵌套匿名循环
(if (< j j-lim)
(do
(work)
(recur (inc j))) 回滚到最内层循环
(recur-to iloop (inc i)))) 回滚到ilop
(完成))))
并且,如果每个 recur-to 形式相对于其所有封装循环结构(直至目标及其包含的目标,包含目标本身)都处于尾递归位置,并且传递给每个 recur-to 形式的参数数与目标循环的局部变量数相匹配(以及加上一个前导循环名称参数),则应该能够编译并且其行为与 Java 中的嵌套循环相似。
所提出的语法是在 Scheme 的命名 let 上构建的,尽管在语义上
它们相当不同——此提案严格限于以自然方式扩展循环/递归模型为嵌套循环。当然,带有名称的 fn 形式也应该被视为有效的 recur-to 目标。
命名循环和 recur-to 的关键属性
==========================================
在上文所述补丁到位后,以下规则在编译时强制执行
1. 每个 recur-to 形式必须相对于其所有封装循环结构(无论是否有名称,直至包括其目标目标)处于尾递归位置。
2. 指定一个并不出现在 recur-to 形式的封装循环或 fn 形式的名称中的 recur-to 目标是一种错误。
3. 无法跨越 try 进行 recur-to。
4. 传递给 recur-to 的参数数(除初始的目标/标签参数外)必须与目标循环或 fn 形式的形式参数数匹配。
5. 允许遮蔽循环名称;recur-to 只能针对以其尾递归位置相对于它是内部最深的具有给定名称的循环。被遮蔽的命名循环引入的循环局部在遮蔽循环内部仍然可见(除非它们被具有相同名称的其他局部遮蔽)。
注意。循环名称 *不是* 局部变量。特别是,它们既不被局部变量遮蔽,也不会遮蔽具有相同名称的局部变量。这一点值得更长时间的讨论;请参阅本电子邮件末尾的设计选择部分。
内部最深的循环或 fn 形式始终可以使用 plain recur 进行定位,无论它是否有名称。此外,(recur-to nil ...) 等价于 (recur ...)(即使内部最深的循环或 fn 形式实际上是带有名称的),而 (loop nil [...] ...) 等价于 (loop [...])。
实现方法摘要
======================================
此补丁修改了编译器中的循环标签处理,并实现了循环宏的一些必要的调整。
它还向 loop* 特殊形式引入了一个可选的名称参数。(它是可选的主要原因是避免破坏非核心宏直接发出 loop*。)
最后,它将 recur 特殊形式重命名为 recur*;recur 和 recur-to 成为 clojure.core 中定义的宏。有关替代方法的详细信息,请参见下文的设计选择部分。
设计选择
==============
1. 在开发过程中,纯粹出于方便,我在那时有一个单独的接受名称参数的 loop-as 宏。我认为将命名功能直接添加到 loop 是合理的,特别是由于 fn 已经接受一个可选的名称。然而,loop-as 也是一个有效的替代设计。
2. 如果希望避免将现有的 recur 特殊形式重命名为 recur* 并重新实现 recur 作为宏,可以添加一个新的 recur-to 特殊形式。(或者,可以在保持 recur 不变的同时添加 recur-to 作为宏,它将委派到一个新的 recur* 特殊形式。)
3. 如果未来有必要保留将循环名称视为局部变量的选项,那么现在可能最好将它们设为可影响其他变量的和被影响,否则在以后某个时间点将它们提升到局部变量的地位将会引发破坏性变更。为了举例说明这样的未来变更可能是有用的,如果尾调用消除支持在未来JDK规范中出现,有人可能会考虑是否采用类似于Scheme的方法,将循环名称视为绑定到只有一个参数列表的函数的局部变量;如果这个局部变量从没有被引用,那么这种闭包分配可能可以通过优化来移除。
值得注意的是,如果尾调用消除(TCE)支持确实出现了,它将启用一系列替代的开发设计。例如,可以引入类似Scheme的风格命名let作为let宏的一项功能。因此,在我看来,限制循环/recur/recur-to仅用于标签+goto风格的循环似乎是合理的,即使是在假设的具有虚拟机TCE支持的将来也是如此,没有理由赋予循环名称类似局部的待遇;因此,补丁当前采用没有影子交互的方法。
致敬,
Michał