Scala Stream 前置 returns List 而不是 Stream

Scala Stream prepend returns List instead of 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。有人可以解释为什么会这样吗?

tl;博士

这是因为编译器类型推断,当你在两个 Seq 上使用 ++: 时,它只是构造另一个 Seq++: 创建 return 类型参数为 Seq 的构建器,但默认 Seq 构建器为 mutable.ListBuffer 并且其 return 类型为 List[A]这也是 Seq。因此,默认情况下,它会在 builder 中制动 laziness 并且 return 值将是 List[Int] 但 return 类型将是 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 调用,它 returns Builder[B, That]:

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

当我们在两个 Seq[Int] 上调用 ++: 时,我们有默认的 CanBuildFromnewBuilder 序列(来自 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]
}

我们看到,newBuilderscala.collection.immutable.Seq:

调用了 immutable.Seq.newBuilder
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这不是偷懒

决定

因此,为了在串联时保持惰性,您应该为 Stream[Int] 传递自己的 CanBuildFrom,类似这样的东西:

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

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

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