Scala:为什么List上的右折叠导致堆栈溢出而不是左折叠?

时间:2012-12-28 20:47:29

标签: scala

我在String of List上运行了右折(:\),导致堆栈溢出。但当我改变它使用左折叠(/ :)时,它工作正常。为什么呢?

2 个答案:

答案 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库的foldLeftfoldRight的实现(来自@ 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模式然后我们会做一些涉及单个的动作   元素和列表的其余部分。事实证明这是一个非常普遍的问题   模式,所以引入了一些非常有用的功能   封装它。这些函数称为折叠。