从提案电子邮件复制到Clojure/dev谷歌群组
https://groups.google.com/d/msg/clojure-dev/zlMGmv60MVA/beyIRTrhAgAJ
你好,
目前loop/recur仅限于“单层”循环:循环形式可以嵌套在其他循环中,但尚无“回退到外部循环”的功能。
几年前,我提出了一项提案,为Clojure添加对嵌套循环的支持,并提供了一个ClojureScript的proof-of-concept补丁,其中包含我认为足够的语法和语义来使得嵌套循环感觉自然,同时仍为core loop/recur模型的自然扩展,具有相同的显式尾递归优势。
(loop foo [...] ; 带名循环
...
(loop [...] intervening loop
...
(loop [...] innermost 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
https://github.com/michalmarczyk/clojure/tree/recur-to
https://github.com/michalmarczyk/clojure/commit/212ea06d21d3b336ac35480c99170e81defaf956
我在Clojure中实现了一个完整的补丁,以启用所提出的特性(第一个链接是基于当前主分支的分支,即1.9.0-alpha17后的“为下一次开发迭代做准备”的提交;第二个链接是分支当前尖端的链接,作为未来参考)。
https://github.com/michalmarczyk/clojure/tree/recur-to
https://github.com/michalmarczyk/clojure/commit/212ea06d21d3b336ac35480c99170e81defaf956
https://dev.clojure.org/jira/browse/CLJ-2235
此电子邮件的其余部分更详细地说明了提案,以某种严谨的形式表述其关键特性,简要总结了实现方法,并讨论了补丁中做出的某些设计选择。
概述
========
想法是,人们可以编写例如代码。
(let [m (two-dimensional-matrix)]
(loop iloop [i 0] named loop
(if (< i i-lim)
(loop [j 0] nested anonymous loop
(if (< j j-lim)
(do
(work)
(recur (inc j))) recur to the innermost loop
(recur-to iloop (inc i)))) ; recur to iloop
(done))))
并且,前提是每个 recur-to 形式与包含其的所有循环形式(包括其目标)都是尾位置的,并且提供给每个 recur-to 形式的参数数量与目标循环的局部变量数量相匹配(对于引导循环名称参数额外加一),则应该可以编译并且行为与 Java 中的嵌套循环非常相似。
所提出的语法在 Scheme 的命名 let 上进行了建模,虽然在语义上
它们有很大不同 - 本提议仅限于以自然的方式将循环/递归模型扩展为嵌套循环。当然,命名 fn 形式也应该是有效的 recur-to 目标。
命名循环和 recur-to 的关键属性
==========================================
假设将上述补丁应用于,编译时会强制执行以下规则
1. 每个 recur-to 形式都必须与包含其的所有循环形式(无论是命名还是非命名)的尾位置相匹配,直到包括其目标(这可能是命名循环或 fn 形式)。
2. 指定一个 recur-to 目标,而它并不是 recur-to 形式的包含循环或 fn 形式的名称之一,这是一个错误。
无法在 try 之间递归。
9. 将recur-to传递给超出初始目标/标签参数的参数数量必须与目标循环或 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 的同时添加 recur-to 作为将 recur 代理到新 recur* 特殊形式的宏。)
3. 如果未来希望保留将循环名视为局部变量的选项,那么现在可能更倾向于将它们设置为影子和被影子局部变量,否则在未来将其提升为局部变量的状态将导致不兼容的更改。为了举例说明这种未来更改可能是有用的,如果未来JDK规范中有了尾调用消除的支持,那么可以考虑是否采用类似于Scheme的方法,将循环名视为一个仅有一个参数列表(对应循环局部变量)的局部变量函数;如果这个局部变量 never 引用,那么这种 closure 分配可能会被优化掉。
需要指出的是,如果TCE(尾调用消除)支持最终实现,它将启用一系列的替代设计方案。例如,可以引入类似Scheme的命名let作为let宏的一个特性。因此在我看来说,限制loop/recur/recur-to仅用于label+goto风格的循环是合理的,即使在假设的未来有VM TCE支持的情况下,也没有理由将循环名视为局部变量;因此,该补丁当前采用不进行阴影交互的方法。
致谢,
Michał