我在String of List上运行了右折(:\),导致堆栈溢出。但当我改变它使用左折叠(/ :)时,它工作正常。为什么呢?
答案 0 :(得分:10)
由于源是开放的,您实际上可以在LinearSeqOptimized.scala中看到实现:
override /*TraversableLike*/
def foldLeft[B](z: B)(f: (B, A) => B): B = {
var acc = z
var these = this
while (!these.isEmpty) {
acc = f(acc, these.head)
these = these.tail
}
acc
}
override /*IterableLike*/
def foldRight[B](z: B)(f: (A, B) => B): B =
if (this.isEmpty) z
else f(head, tail.foldRight(z)(f))
您会注意到foldLeft
使用while循环而foldRight
使用递归。循环策略非常有效,但递归需要在列表中为列表中的每个元素推送一个帧(当它遍历到结尾时)。这就是为什么foldLeft
工作正常但foldRight
导致堆栈溢出的原因。
答案 1 :(得分:1)
Fold
是一组常用函数,它们遍历递归数据结构,通常会产生单个值(reference)。在序列和列表中,FoldLeft(在一般意义上)是尾递归的,因此可以对其进行优化。 FoldRight不是尾递归的,因此无法进行尾调用优化。然而,它确实具有能够应用于无限级数的好处。
来自scala库的foldLeft
和foldRight
的实现(来自@ dhg的回复)反映了这种优化/缺乏。 foldLeft
已使用while循环手动进行尾调用优化。 foldRight
不能。{/ p>
override /*TraversableLike*/
def foldLeft[B](z: B)(f: (B, A) => B): B = {
var acc = z
var these = this
while (!these.isEmpty) {
acc = f(acc, these.head)
these = these.tail
}
acc
}
override /*IterableLike*/
def foldRight[B](z: B)(f: (A, B) => B): B =
if (this.isEmpty) z
else f(head, tail.foldRight(z)(f))
我相信Programming in Scala, Second Edition by Odersky, Spoon, Venners中有一个关于折叠的部分描述了列表中的foldLeft
是如何尾递归的,而有可能是无限列表上的foldRight
。不幸的是,我现在还没有提供页码,等等。如果不是,那么证明这一点并不是很难。
另请参阅Learn You a Haskell for Great Good by Miran Lipovača
中的折叠部分当我们处理递归时,我们注意到了一个主题 在列表上运行的许多递归函数中。 通常,我们对空列表有一个边缘情况。我们介绍一下 x:xs模式然后我们会做一些涉及单个的动作 元素和列表的其余部分。事实证明这是一个非常普遍的问题 模式,所以引入了一些非常有用的功能 封装它。这些函数称为折叠。