尾部调用优化失败时 Clojure 警告/错误

发布于 2024-08-30 06:11:46 字数 729 浏览 10 评论 0原文

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

Clojure 中是否有关于循环/递归的类似功能?

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

(recur exprs*)
按顺序计算表达式,然后并行地将递归点的绑定重新绑定到表达式的值。如果递归点是 fn 方法,那么它会重新绑定参数。如果递归点是循环,则它会重新绑定循环绑定。然后执行跳回到递归点。递归表达式必须与递归点的元数完全匹配。特别是,如果递归点是可变参数 fn 方法的顶部,则不会收集其余参数 - 应传递单个 seq (或 null)。 在尾部位置以外的位置重复出现是错误

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

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

In Scala 2.8.x, a new annotation (@tailrec) has been added that gives a compile-time error if the compiler cannot perform a tail-call optimization on the annotated method.

Is there some similar facility in Clojure with respect to loop/recur?

EDIT:
After reading the first answer to my question (thanks, Bozhidar Batsov) and further searching in the Clojure docs, I came across this:

(recur exprs*)
Evaluates the exprs in order, then, in parallel, rebinds the bindings of the recursion point to the values of the exprs. If the recursion point was a fn method, then it rebinds the params. If the recursion point was a loop, then it rebinds the loop bindings. Execution then jumps back to the recursion point. The recur expression must match the arity of the recursion point exactly. In particular, if the recursion point was the top of a variadic fn method, there is no gathering of rest args - a single seq (or null) should be passed. recur in other than a tail position is an error.

Note that recur is the only non-stack-consuming looping construct in Clojure. There is no tail-call optimization and the use of self-calls for looping of unknown bounds is discouraged. recur is functional and its use in tail-position is verified by the compiler [emphasis is mine].

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

永不分离 2024-09-06 06:11:46

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

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

请参阅此博客条目了解以下内容的讨论Scala 中的 TCO 和蹦床。 Clojure 具有完全相同的功能来促进非堆栈消耗(=尾调用优化)递归; 这包括当用户代码尝试在非尾部位置调用recur时抛出编译时错误

Actually the situation in Scala w.r.t. Tail Call Optimisation is the same as in Clojure: it is possible to perform it in simple situations, such as self-recursion, but not in general situations, such as calling an arbitrary function in tail position.

This is due to the way the JVM works -- for TCO to work on the JVM, the JVM itself would have to support it, which it currently doesn't (though this might change when JDK7 is released).

See e.g. this blog entry for a discussion of TCO and trampolining in Scala. Clojure has exactly the same features to facilitate non-stack-consuming (= tail-call-optimised) recursion; this includes throwing a compile-time error when user code tries to call recur in non-tail position.

不语却知心 2024-09-06 06:11:46

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

在没有可变本地的情况下
变量、循环和迭代必须
采用与以下不同的形式
内置 for 或 while 的语言
由以下结构控制
改变状态。在功能上
语言的循环和迭代是
通过递归替换/实现
函数调用。很多这样的语言
保证函数调用
尾部位置不消耗堆栈
空间,从而递归循环
利用恒定的空间。自从 Clojure 以来
使用 Java 调用约定,它
不能也不会做出同样的事情
尾调用优化保证。
相反,它提供了重复的特殊
运算符,它执行常数空间
通过重新绑定和递归循环
跳转到最近的封闭循环
或功能框架。虽然不一样
一般作为尾调用优化,它
让大部分同样优雅
构建并提供优势
检查重复调用是否可以
只发生在尾部位置。

There is no tail-call optimization when you use loop/recur AFAIK. A quote from the official docs:

In the absence of mutable local
variables, looping and iteration must
take a different form than in
languages with built-in for or while
constructs that are controlled by
changing state. In functional
languages looping and iteration are
replaced/implemented via recursive
function calls. Many such languages
guarantee that function calls made in
tail position do not consume stack
space, and thus recursive loops
utilize constant space. Since Clojure
uses the Java calling conventions, it
cannot, and does not, make the same
tail call optimization guarantees.
Instead, it provides the recur special
operator, which does constant-space
recursive looping by rebinding and
jumping to the nearest enclosing loop
or function frame. While not as
general as tail-call-optimization, it
allows most of the same elegant
constructs, and offers the advantage
of checking that calls to recur can
only happen in a tail position.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文