从提交给 Clojure/dev Google 群组的提案邮件中复制
https://groups.google.com/d/msg/clojure-dev/zlMGmv60MVA/beyIRTrhAgAJ
你好,
目前 loop/recur 仅限于 "单层" 循环:循环形式可以出现在其他循环形式内,但没有 "递归到外部循环" 的功能。
几年前我提出了一项提案,为 Clojure 添加对嵌套循环的支持,并为 ClojureScript 提供了概念验证补丁,语义和语法我认为足够让嵌套循环感觉自然,同时 Still 是核心 loop/recur 模型的自然扩展,具有相同的尾递归好处
(loop foo [...] ; 命名循环
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 之后的 "为下一个开发迭代做好准备" 的提交;第二个链接是该分支的当前 tip,供将来参考)
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)
(work)
(recur (inc j))) )
(recur-to iloop (inc i)))) ; 递归到 iloop
并且,如果每个 recur-to 形式相对于所有包含它的循环形式都位于尾部(包括其目标),且向每个 recur-to 形式传递的参数数量与目标循环的局部变量数相匹配(再加上领先循环名称参数),则应能编译并具有类似 Java 中嵌套循环的行为。
建议的语法以 Scheme 的命名 let 为模型,虽然在语义上
两者有很大区别 - 此提议严格限于以自然的方式将循环/递归模型扩展到嵌套循环。当然,命名 fn 形式也应该是有效的 recur-to 目标。
命名循环和 recur-to 的关键特性
==========================================
在上面的补丁实施后,以下规则在编译时强制执行
1. 每个 recur-to 形式都必须相对于其所有包含的循环形式(无论是命名还是未命名的)位于尾部,直至包括其目标(可能是命名循环或 fn 形式)。
2. 指定一个 recur-to 目标,该目标不在其包含的循环或 fn 形式的名称列表中,这是错误的,并且它需要相对于其位于尾部的循环或 fn 形式。
3. 无法在 try 语句中 recur。
4. 传递给 recur-to 的参数数量(除了初始目标/标签参数外)必须匹配目标循环或 fn 形式的形式参数数量。
5. 允许覆盖循环名称;recur-to 可以只针对名称最内的循环。此外,loop 内部通过引入的命名循环引入的循环局部变量在覆盖的循环中仍然可见(除非它们被同样命名的局部变量覆盖)。
注意。循环名称不是局部变量。特别是,它们既不被局部变量覆盖也不覆盖具有相同名称的局部变量。这一点值得更深入的讨论;请参见邮箱末尾的“设计选择”部分。
最内层的循环或 fn 形式始终可以使用纯 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-to 作为将 recur 代理到新 recur* 特殊形式的宏的同时,可以保留 recur 如其当前状态。)
3. 如果将来希望保留将循环命名作为本地变量的选项,现在最好是将它们视为被本地变量阴影和被阴影,因为否则在以后提升它们的本地变量状态将会是一个破坏性变化。为了举例说明这种未来的变化可能是有用的,如果尾调用消除支持在未来JDK规范中到来, someone might consider whether adopting a Scheme-like approach with the loop name treated as a local bound to a function with a single argument list corresponding to the loop locals of the named loop would be useful; 如果本地变量从未被引用,那么这种变化可能不会包含任何闭环分配,这或许可以被优化掉。
值得注意的是,如果尾调用消除支持确实出现,它将启用一系列替代设计。例如,可以作为let宏的特性的Scheme风格的命名let引入。因此,在我看来,限制循环/recur/to只能使用标签+goto风格的循环似乎是合理的,即使在假设的未来中具有虚拟机尾调用消除支持,也没有理由给予循环命名类似本地的待遇;因此,补丁目前采用无阴影交互的方法。
谨致问候,
Michał