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]
上调用 ++:
时,我们有默认的 CanBuildFrom
和 newBuilder
序列(来自 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]
}
我们看到,newBuilder 从 scala.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]
我有一个 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]
上调用 ++:
时,我们有默认的 CanBuildFrom
和 newBuilder
序列(来自 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]
}
我们看到,newBuilder 从 scala.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]