在Frege中优化尾调用。我知道在Java和编译为JVM字节码的语言中都没有TCO,如Clojure和Scala。弗雷格怎么样?
答案 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)
rdd.saveAsSequenceFile(path, Some(classOf[BZip2Codec]))
注意,我没有足够的声誉直接评论。查找我在原始问题的评论中回复的评论。