从 Clojure/dev Google 群组的提案邮件复制而来
https://groups.google.com/d/msg/clojure-dev/zlMGmv60MVA/beyIRTrhAgAJ
你好,
目前,loop/recur 只限于“单层”循环:循环形式可以出现在其他循环形式内部,但没有“重新回到外层循环”的功能。
几年前,我提出了一项提案,旨在为 Clojure 添加对嵌套循环的支持,并提供了一个针对 ClojureScript 的概念验证补丁,其语法和语义在我看来足够自然地实现嵌套循环,同时保留核心 loop/recur 模型的自然扩展,具有相同的显式尾递归优势
(loop foo [...] ; 命名循环
...
(loop [...] 一条中间循环
...
(loop [...] 一个最内层循环
...
(recur-to 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 能够实现所提议的功能(第一个链接是基于当前主分支的分支,即 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
 ;(工作)
(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 形式名称的 recur-to 目标是错误的。
不可能在 try 跨越 recur。
4. 传递给 recur 的参数数量超出初始目标/标签参数的数量必须与目标循环或 fn 形式的形式参数数量匹配。
5. 允许现金循环名称;recur-to 只能针对给定名称的最内层循环。此外,通过命名循环引入的循环局部变量在阴影循环中仍然可见(除非它们自己也由同名的局部变量阴影)。
注意。循环名称不是局部变量。特别是,它们既不阴影,也不被同名的局部变量阴影。这一点值得更深入的讨论;请参阅此电子邮件末尾的设计选择部分。
最内层循环或 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。)
3. 如果希望在将来保留将循环名作为局部变量的选项,那么现在将它们作为本地变量的影子可能是更好的选择,因为否则在以后将它们提升到本地变量的地位将是一个破坏性的变化。为了说明为什么未来的这种变化可能会很有用,假设在未来的JDK规范中出现了尾调用消除支持,人们可能会考虑是否采用类似于Scheme的方法,将循环名作为一个局部变量绑定到只有一个arglist的函数,这个arglist对应于命名循环的局部变量;如果本地变量从未被引用,这种闭包分配可能可以优化掉。
值得注意的是,如果TCE支持确实存在,它将使得一系列替代设计成为可能。例如,可以作为let宏的一个特性引入Scheme风格的命名lets。因此,在我看来,将循环/recur/recur-to限制为只使用标签+goto风格的循环似乎是合理的,即使是在假设的未来有VM TCE支持的情况下,也没有理由将循环名称享有类似局部的待遇;因此,补丁当前采用的没有影子交互的方法。
问候,
Michał