【问题标题】:Does Frege perform tail call optimization?Frege 是否执行尾调用优化?
【发布时间】:2012-04-18 00:24:00
【问题描述】:

在 Frege 中是否优化了尾调用。我知道 Java 和编译成 JVM 字节码的语言(如 Clojure 和 Scala)都没有 TCO。弗雷格呢?

【问题讨论】:

  • 问题的标题是人们首先看到的,而“TCO”只是另一个 TLA。
  • 值得一提的是Scala有TCO,一些JVM(比如IBM的)也实现了它。
  • @Landei 这是 Scala 的新功能吗? Scala 已经很久不支持 TCO 了!
  • IIRC,Scala 从一开始就有(简单的)TCO。试试def foo {println("foo!"); foo} - 如果你调用它,它不会以StackOverflowException 停止。

标签: clojure functional-programming jvm jvm-languages frege


【解决方案1】:

Frege 通过简单地生成 while 循环来进行尾递归优化。

一般尾调用是通过懒惰“顺便”处理的。如果编译器看到对已知(间接)递归的可疑函数的尾调用,则返回惰性结果(thunk)。因此,调用该函数的真正负担在于调用者。这样可以避免深度取决于数据的堆栈。

话虽如此,静态堆栈深度在函数式语言中已经比在 Java 中更深了。因此,需要为某些程序提供更大的堆栈(即使用 -Xss1m)。

存在一些病态的情况,即构建大的 thunk 并且在评估它们时会发生堆栈溢出。一个臭名昭著的例子是 foldl 函数(与 Haskell 中的问题相同)。因此,Frege 中的标准左折叠是 fold,它在累加器中是尾递归和严格的,因此可以在恒定堆栈空间中工作(如 Haskells foldl')。

以下程序应该堆栈溢出,但在 2 或 3 秒后打印“false”:

module Test
    -- inline (odd) 
  where

even 0 = true
even 1 = false
even n = odd (pred n)

odd n = even (pred n)

main args =  println (even 123_456_789)

它的工作原理如下:println 必须有一个要打印的值,因此尝试评估(甚至 n)。但它得到的只是一个 thunk to (odd (pred n))。因此它试图评估这个 thunk,它得到另一个 thunk 到 (even (pred (pred n)))。 even 必须评估 (pred (pred n)) 以查看参数是 0 还是 1,然后返回另一个已经评估 n-2 的 thunk (odd (pred (n-2))。 这样,所有调用(在 JVM 级别)都是在 println 中完成的。任何时候都不会真正调用奇数,反之亦然。

如果取消了 inline 指令,则会得到 even 的尾递归版本,结果会快十倍。

不用说,这种笨拙的算法仅用于演示 - 通常会通过位操作来检查均匀性。

这里是另一个版本,是病态的,会堆栈溢出:

even 0 = true
even 1 = false
even n = not . odd  $ n
odd    = even . pred

这里的问题是not 是尾调用并且它的论点是严格的(即,要否定某些东西,你必须首先拥有那个东西)。因此,当计算 even n 时,not 必须完全评估 odd n,而 odd n 又必须完全评估 even (pred n),因此需要 2*n 堆栈帧。

不幸的是,这不会改变,即使 JVM 有一天应该有适当的尾调用。原因是严格函数的参数中的递归。

【讨论】:

  • 惊人的答案,非常感谢!顺便说一句,弗雷格中有严格性注释吗?
猜你喜欢
  • 2010-10-23
  • 2011-07-16
  • 2010-11-16
  • 1970-01-01
  • 2020-10-21
  • 1970-01-01
  • 2017-07-23
  • 2018-08-30
  • 2015-07-20
相关资源
最近更新 更多