9

在 Scala 2.8.x 中,@tailrec添加了一个新的注解 ( ),如果编译器无法对注解的方法执行尾调用优化,则会给出编译时错误。

Clojure 中是否有一些类似的设施loop/recur

编辑: 在阅读了我的问题的第一个答案(感谢 Bozhidar Batsov)并在 Clojure 文档中进一步搜索后,我发现了这个:

(recur exprs*)
按顺序计算 expr,然后并行地将递归点的绑定重新绑定到 expr 的值。如果递归点是一个 fn 方法,那么它会重新绑定参数。如果递归点是一个循环,那么它会重新绑定循环绑定。然后执行跳回到递归点。递归表达式必须与递归点的数量完全匹配。特别是,如果递归点是可变参数 fn 方法的顶部,则不会收集其余参数 - 应该传递单个 seq(或 null)。recur in other than a tail position 是错误的

请注意,recur 是 Clojure 中唯一不消耗堆栈的循环结构。没有尾调用优化,不鼓励使用自调用来循环未知边界。recur 是功能性的,它在尾部位置的使用由编译器验证[重点是我的]。

(def factorial
  (fn [n]
    (loop [cnt n acc 1]
       (if (zero? cnt)
            acc
          (recur (dec cnt) (* acc cnt))))))
4

2 回答 2

6

实际上Scala wrt尾调用优化中的情况与Clojure中的情况相同:可以在简单的情况下执行它,例如自递归,但不能在一般情况下执行,例如在尾部位置调用任意函数。

这是由于 JVM 的工作方式——为了使 TCO 在 JVM 上工作,JVM 本身必须支持它,而目前它不支持它(尽管在 JDK7 发布时这可能会改变)。

有关Scala 中 TCO 和蹦床的讨论,请参阅此博客条目。Clojure 具有完全相同的特性来促进非堆栈消耗(=尾调用优化)递归;这包括当用户代码尝试recur在非尾部位置调用时引发编译时错误。

于 2010-04-26T15:29:17.847 回答
4

使用循环/递归 AFAIK 时没有尾调用优化。来自官方文档的引用:

在没有可变局部变量的情况下,循环和迭代必须采用与具有通过改变状态控制的内置 for 或 while 结构的语言不同的形式。在函数式语言中,循环和迭代是通过递归函数调用来替换/实现的。许多这样的语言保证在尾部位置进行的函数调用不会消耗堆栈空间,因此递归循环使用恒定空间。由于 Clojure 使用 Java 调用约定,它不能也不会做出相同的尾调用优化保证。相反,它提供了 recur 特殊运算符,该运算符通过重新绑定并跳转到最近的封闭循环或函数框架来执行恒定空间递归循环。虽然不像尾调用优化那样通用,

于 2010-04-26T11:14:08.403 回答