我在一个String列表上进行了右折叠(:\),导致堆栈溢出。但是当我改用左折叠(/:)时,它就正常工作了。为什么?
我在一个String列表上进行了右折叠(:\),导致堆栈溢出。但是当我改用左折叠(/:)时,它就正常工作了。为什么?
由于源代码是开放的,因此您实际上可以在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
会导致堆栈溢出的原因。
Fold
是一组通用的常用函数,它们遍历递归数据结构并通常产生单个值(reference)。在序列和列表上,FoldLeft(在一般意义上)是尾递归的,因此可以进行优化。FoldRight不是尾递归,因此无法进行尾调用优化。但它可能有将被应用于无限序列的好处。
Scala库中foldLeft
和foldRight
的实现(从@dhg的答案中盗版)反映了这种优化/缺乏优化。使用while循环手动尾调用优化了foldLeft
。foldRight
则不能。
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模式,然后执行涉及单个元素和其余列表的某些操作。事实证明,这是一个非常常见的模式,因此引入了几个非常有用的函数来封装它。这些函数称为折叠。
def foldRight[B](z: B)(op: (A, B) => B): B = reversed.foldLeft(z)((x, y) => op(y, x))
。难道没有一个启发式方法来转到 /:? 吗? - som-snytt