练习:在Scala中实现Stream

时间:2012-08-16 08:48:41

标签: scala functional-programming

我正在关注本书Functional programming in Scala,特别是您实现简单Stream特征和伴随对象的部分。作为参考,这是我们迄今为止在伴侣对象中所拥有的内容

object Stream {
  def empty[A]: Stream[A] =
    new Stream[A] {
      def uncons = None
    }

  def cons[A](hd: => A, tl: => Stream[A]): Stream[A] =
    new Stream[A] {
      lazy val uncons = Some((hd, tl))
    }

  def apply[A](as: A*): Stream[A] =
    if (as.isEmpty)
      empty
    else
      cons(as.head, apply(as.tail: _*))
}

到目前为止的特点:

trait Stream[A] {
  import Stream._

  def uncons: Option[(A, Stream[A])]

  def toList: List[A] = uncons match {
    case None => Nil: List[A]
    case Some((a, as)) => a :: as.toList
  }

  def #::(a: => A) = cons(a, this)

  def take(n: Int): Stream[A] =
    if (n <= 0)
      empty
    else (
      uncons
      map { case (a, as) => a #:: (as take (n - 1)) }
      getOrElse empty
    )
}

下一个练习要求我为takeWhile编写一个实现,我认为以下是

  def takeWhile(f: A => Boolean): Stream[A] = (
    uncons
    map { case (a, as) => if (f(a)) (a #:: (as takeWhile f)) else empty }
    getOrElse empty
  )

不幸的是,似乎我得到了一个我无法追踪的差异错误:

error: type mismatch;  found   : Stream[_2] where type _2 <: A 
required: Stream[A]
Note: _2 <: A, but trait Stream is invariant in type A.
You may wish to define A as +A instead. (SLS 4.5)
           getOrElse empty
           ^

我可以添加方差注释,但在此之前我想了解这里出了什么问题。有什么建议吗?

2 个答案:

答案 0 :(得分:2)

这似乎是类型推断的问题,因为如果您明确指定子表达式uncons map { case (a, as) => if (f(a)) (a #:: (as takeWhile f)) else empty }的类型,它就有效。

def takeWhile(f: A => Boolean): Stream[A] = {
  val mapped:Option[Stream[A]] = uncons map {
    case (a, as) => if (f(a)) (a #:: (as takeWhile f)) else empty
  }
  mapped getOrElse empty
}

答案 1 :(得分:1)

要完成另一个答案,此行empty

map { case (a, as) => if (f(a)) (a #:: (as takeWhile f)) else empty }

被推断为empty[Nothing],这意味着(a #:: (as takeWhile f)) else empty被推断为Stream[Foo <: A],并且因为Stream[A]是预期的而Stream是不变的,所以你有一个错误。

因此,这为我们提供了解决此问题的最简洁方法:只需注释empty

map { case (a, as) => if (f(a)) (a #:: (as takeWhile f)) else empty[A] }

然后它编译得很好。

原始Stream不会发生这种情况,因为它是协变的,因此您实际上要求Stream.empty成为Stream[Nothing](就像Nil一样{{1} 1}}),或者你不在乎。

现在,关于它被推断为List[Nothing]而不是empty[Nothing]的确切原因,这可能隐藏在SLS 6.26.4“本地类型推断”的某处,但这一部分实际上不能被指责为容易阅读...

作为一项规则,一旦你打电话给方法,总是要怀疑:

  • 具有类型参数,其唯一的推断方式是期望的返回类型(通常因为它们没有参数),
  • 与此同时,预期的返回类型本身应该从其他地方推断出来。