2

这是我在 Scala的函数式编程练习中toListStream课程编写的尝试。

def toList[A](stream: Stream[A]): List[A] = {
    def go(s: Stream[A], acc: List[A]): List[A] = s match {
        case x #:: xs => go(xs, acc :+ x)
        case _ => acc
    }
    go(stream, Nil)
}

根据对这篇文章的阅读(但不是全部理解) ,我不确定我的模式匹配是否正确。特别是,我担心我的第一个案例会导致立即评估流的尾部。

从概念上讲,我认为我需要实现toList每个递归步骤将流的头部添加到列表的位置,而不是评估每个步骤的尾部。

我对这种理解和上述实现是否正确?

4

1 回答 1

4

文档和源代码Stream很好地解决了这个问题。

的来源#::是:

  object #:: {
    def unapply[A](xs: Stream[A]): Option[(A, Stream[A])] =
      if (xs.isEmpty) None
      else Some((xs.head, xs.tail))
  }

相关部分是Some((xs.head, xs.tail)). 来源tail声明:_

请注意,此方法不会强制评估 ,Stream而仅返回惰性结果。

基于文档xs不会在案例声明中得到评估case x #:: xs。如果这种情况匹配,则go(xs, acc :+ x)调用 then。xs这里本质s.tail上是没有如上所述评估的。

您链接到的帖子中的代码不同之处在于 tail( a Stream) 用于递归调用,merge然后unapply在将其解构为头部和尾部时调用它。由于head是 aStream它被评估并且将在足够大的输入上堆栈溢出。@Didier Dupont 也很好地说明了这一点:

另请注意,在 Scala Stream 中,虽然尾部是惰性的,但头部不是。当你有一个(非空的)流时,必须知道头部。这意味着当你得到流的尾部时,它本身就是一个流,它的头部,即原始流的第二个元素,必须被计算。这有时是有问题的,但在你的例子中,你甚至在到达那里之前就失败了。

除非是 . head_ _ _ 即流的流。另一篇文章中的代码通过递归调用创建了这种情况。tailxsAStreammerge

于 2013-11-04T05:48:59.100 回答