Frege是否执行尾调用优化?

时间:2012-04-04 09:45:05

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

在Frege中优化尾调用。我知道在Java和编译为JVM字节码的语言中都没有TCO,如Clojure和Scala。弗雷格怎么样?

2 个答案:

答案 0 :(得分:27)

Frege通过简单地生成while循环来执行Tail Recursion Optimization。

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

话虽如此,静态堆栈深度本质上在功能语言中比在Java中更深。因此,某些程序需要被赋予更大的堆栈(即使用-Xss1m)。

存在病态情况,其中构建了大的thunk,并且当它们被评估时,将发生堆栈溢出。一个臭名昭着的例子是foldl函数(与Haskell中的问题相同)。因此,Frege中的标准左侧折叠是折叠,它在累加器中是尾递归和严格的,因此在常量堆栈空间中工作(如Haskells foldl')。

以下程序堆栈溢出但在2或3s后打印“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(奇数(pred n))。因此它试图评估这个thunk,它得到另一个thunk(even(pred(pred n)))。甚至必须评估(pred(pred n))以查看参数是0还是1,然后返回另一个thunk(奇数(pred(n-2)),其中n-2已经被评估。 这样,所有调用(在JVM级别)都是在println中完成的。在任何时候都不会实际调用奇数,反之亦然。

如果取消注释内联指令,则会获得偶数的尾递归版本,并且结果的获得速度提高十倍。

毋庸置疑,这种笨拙的算法仅用于演示 - 通常可以通过一点操作检查均匀性。

这是另一个版本,它是病态的并且会堆叠溢出:

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

问题在于not是尾调用,它的参数是严格的(即,要否定某些东西,你必须首先拥有它)。因此,在计算even n时,not必须完全评估odd n,而even (pred n)必须完全评估{{1}},因此需要2 * n个堆栈帧。< / p>

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

答案 1 :(得分:1)

在Scala中不完全支持@Landei TCO ...试试这个。

rdd.saveAsSequenceFile(path, Some(classOf[BZip2Codec]))

注意,我没有足够的声誉直接评论。查找我在原始问题的评论中回复的评论。