首页 > 解决方案 > Scala Stream prepend 返回 List 而不是 Stream

问题描述

我有一个 Seq,x和一个 Stream,y并且我希望预先x获得y一个新的 Stream。但是,静态类型y导致 Stream 立即被评估,我很困惑为什么会这样。这是一个例子:

val x: Seq[Int] = Seq(1, 2, 3)
val y: Seq[Int] = Stream(4, 5, 6)
val z = x ++: y // z has dynamic type List instead of Stream

由于该++:方法是在 Stream 实例上调用的,因此我希望得到一个 Stream,但结果却是一个 List。有人可以解释为什么会这样吗?

标签: scalascala-streams

解决方案


tl;博士

这是因为编译器类型推断,当你++:在两个上使用它时,Seq它只是构造另一个Seq. ++:创建返回类型 param 的构建器Seq,但默认Seq构建器是mutable.ListBuffer,它的返回类型List[A]也是Seq。因此,默认情况下,它会在builder内部制动惰性,返回值将是但返回类型将是.List[Int]Seq[Int]

问题调查

让我们看一下++:签名(例如在 scala 2.12.10 中):

def ++:[B >: A, That](that: TraversableOnce[B])(implicit bf: CanBuildFrom[Repr, B, That]): That = {
    val b = bf(repr)
    if (that.isInstanceOf[IndexedSeqLike[_, _]]) b.sizeHint(this, that.size)
    b ++= that
    b ++= thisCollection
    b.result
  }

在这里我们看到隐含的论点:bf: CanBuildFrom[Repr, B, That]. 排队:

val b = bf(repr) // b is Builder[B, That]

这里CanBuildFrom.apply调用,它返回Builder[B, That]

trait CanBuildFrom[-From, -Elem, +To] {
  def apply(from: From): Builder[Elem, To]
}

当我们调用++:两个时,Seq[Int]我们有 defaultCanBuildFromnewBuilderfor 序列(来自scala.collection.Seq):

object Seq extends SeqFactory[Seq] {
  /** $genericCanBuildFromInfo */
  implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, Seq[A]] = ReusableCBF.asInstanceOf[GenericCanBuildFrom[A]]

  def newBuilder[A]: Builder[A, Seq[A]] = immutable.Seq.newBuilder[A]
}

我们看到,newBuilderimmutable.Seq.newBuilder从以下位置调用scala.collection.immutable.Seq

object Seq extends SeqFactory[Seq] {
  /** genericCanBuildFromInfo */
  implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, Seq[A]] = ReusableCBF.asInstanceOf[GenericCanBuildFrom[A]]
  def newBuilder[A]: Builder[A, Seq[A]] = new mutable.ListBuffer
}

我们看看mutable.ListBuffer哪个不是懒惰的。

决定

因此,为了在连接时保持懒惰,您应该传递自己的CanBuildFromfor Stream[Int],如下所示:

import scala.collection.generic.CanBuildFrom
import scala.collection.mutable
import scala.collection.mutable.Builder

val x: Seq[Int] = Seq(1, 2, 3)
val y: Seq[Int] = Stream(4, 5, 6)
implicit val cbf = new CanBuildFrom[Seq[Int], Int, Stream[Int]] {
  override def apply(from: Seq[Int]): Builder[Int, Stream[Int]] =
    new mutable.LazyBuilder[Int, Stream[Int]] {
      override def result() = from.toStream
    }

  override def apply(): mutable.Builder[Int, Stream[Int]] = Stream.newBuilder[Int]
}
val z = x ++:(y) // not it will be Stream(1, ?)

或者您可以只从两个序列中制作流:

val x: Seq[Int] = Seq(1, 2, 3)
val y: Seq[Int] = Stream(4, 5, 6)
val z = x.toStream ++: y.toStream

CanBuildFrom并且编译器将从对象中找到隐含的Stream,这是惰性的:

implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, Stream[A]] = new StreamCanBuildFrom[A]

推荐阅读