【问题标题】:Why is Seq.newBuilder returning a ListBuffer?为什么 Seq.newBuilder 返回一个 ListBuffer?
【发布时间】:2013-07-21 03:01:46
【问题描述】:

看着

val sb = Seq.newBuilder[Int]
println(sb.getClass.getName)
sb += 1
sb += 2
val s = sb.result()
println(s.getClass.getName)

输出是

scala.collection.mutable.ListBuffer
scala.collection.immutable.$colon$colon

使用 Scala 2.10.1。

例如,我希望Seq.newBuilder 返回VectorBuilder。如果结果显式输入到Seq,则由CanBuildFrom 返回:

def build[T, C <: Iterable[T]](x: T, y: T)
                              (implicit cbf: CanBuildFrom[Nothing, T, C]): C = {
  val b = cbf()
  println(b.getClass.getName)
  b += x
  b += y
  b.result()
}

val s: Seq[Int] = build(1, 2)
println(s.getClass.getName) // scala.collection.immutable.Vector

在这种情况下,构建器是VectorBuilder,结果的类是Vector

所以我明确地想要构建一个Seq,但根据Scala collection memory footprint characteristics,结果是一个需要更多内存的List

那么为什么Seq.newBuilder 会返回一个ListBuffer,它最终会给出一个List

【问题讨论】:

  • 有点像你的stackoverflow.com/q/17599652/1296806 的骗子,但我真的不明白你对CanBuildFrom 的评论是什么意思。这很有趣。顺便说一句,我认为如果一个问题得到相同的答案,它可能是一个骗局。不要忘记阅读概述文档和 Spiewak stackoverflow.com/a/6934116/1296806
  • @som-snytt 如果ListSeq 的默认实现,那么我希望CanBuildFrom 也会返回List,如果结果输入为@987654341 @(为了一致性)。我现在可以添加此评论,因为其中一个答案表明ListSeq 的默认值。至于 Spiewak 的回答,我以前读过,但这又提出了另一个问题:SeqList 都是特征,而 Vector 不是。但我想要的只是一个SeqIterable 有顺序),例如作为方法的结果。最有效的实现。应用于履行合同。
  • “最有效”的概念是上下文相关的,因此人们争论不休。我认为从历史上看,Lisp 类型是根据 List 来思考的。但是,对于 CanBuildFrom,您从什么开始构建显然很重要,因此 CanBuild From,因此您并没有摆脱困境。但我明白你的意思:你想要求一个默认的 CBF。好吧,你得到的默认值恰好是为一个常见的用例 String 制造的,你显然想要一个索引 Seq。
  • 我不敢相信 SO 对 cme​​ts 应用 Twitterish 长度限制。无论如何:为什么?我不知道。您可能会争辩说应该从左到右处理字符串;除了人们假设字符串是由类似数组的东西支持的。我们一直在做 s.indexOf(c)。
  • @som-snytt 上下文是 Seq 特征。所以我想为此获得“最有效”的实现。

标签: scala scala-collections


【解决方案1】:

Scala Collections API 非常复杂,层次结构丰富。每个级别都代表某种新的抽象。 Seq 特征分为两个不同的子特征,它们为性能提供不同的保证 (ref.):

  1. IndexedSeq 提供快速随机访问元素和快速长度操作。 IndexedSeq 的代表之一是 Vector

  2. LinearSeq 仅通过头部提供对第一个元素的快速访问,但也具有快速尾部操作。 LinearSeq 的代表之一是 List

由于Seq 的当前默认实现是ListSeq.newBuilder 将返回ListBuffer。但是,如果您想使用Vector,您可以使用Vector.newBuilder[T]IndexedSeq.newBuilder[T]

scala> scala.collection.immutable.IndexedSeq.newBuilder[Int]
res0: scala.collection.mutable.Builder[Int,scala.collection.immutable.IndexedSeq[Int]] = scala.collection.immutable.VectorBuilder@1fb10a9f

scala> scala.collection.immutable.Vector.newBuilder[Int]
res1: scala.collection.mutable.Builder[Int,scala.collection.immutable.Vector[Int]] = scala.collection.immutable.VectorBuilder@3efe9969

【讨论】:

  • 我知道这两个子特征,但实际上我不知道它就这么简单:Seq 的默认实现是List。我刚才没看到那条信息,因为我只是从Seqtrait的角度看的,不过当然是的,下次再看伴侣也是如此。
【解决方案2】:

默认的Seq 实现是List

Seq(1, 2, 3)  // -> List(1, 2, 3)

...因此ListBuffer 是正确的构建器。如果你想要Vector,请使用Vector.newBuilderIndexedSeq.newBuilder

【讨论】:

  • 是的,就是这么简单:Seq companion 的文档中明确说明了这一点,而我只阅读了 Seq 特征的文档。
【解决方案3】:

好的,但你不会相信的。为您的 CanBuildFrom 反例打开 -Yinfer-debug

[search] $line14.$read.$iw.$iw.build[scala.this.Int, Seq[scala.this.Int]](1, 2) with pt=generic.this.CanBuildFrom[scala.this.Nothing,scala.this.Int,Seq[scala.this.Int]] in module class $iw, eligible:
  fallbackStringCanBuildFrom: [T]=> generic.this.CanBuildFrom[String,T,immutable.this.IndexedSeq[T]]
[solve types] solving for T in ?T
inferExprInstance {
  tree      scala.this.Predef.fallbackStringCanBuildFrom[T]
  tree.tpe  generic.this.CanBuildFrom[String,T,immutable.this.IndexedSeq[T]]
  tparams   type T
  pt        generic.this.CanBuildFrom[scala.this.Nothing,scala.this.Int,Seq[scala.this.Int]]
  targs     scala.this.Int
  tvars     =?scala.this.Int
}
[search] considering no tparams (pt contains no tvars) trying generic.this.CanBuildFrom[String,scala.this.Int,immutable.this.IndexedSeq[scala.this.Int]] against pt=generic.this.CanBuildFrom[scala.this.Nothing,scala.this.Int,Seq[scala.this.Int]]
[success] found SearchResult(scala.this.Predef.fallbackStringCanBuildFrom[scala.this.Int], ) for pt generic.this.CanBuildFrom[scala.this.Nothing,scala.this.Int,Seq[scala.this.Int]]
[infer implicit] inferred SearchResult(scala.this.Predef.fallbackStringCanBuildFrom[scala.this.Int], )

确实,

  implicit def fallbackStringCanBuildFrom[T]: CanBuildFrom[String, T, immutable.IndexedSeq[T]] =
    new CanBuildFrom[String, T, immutable.IndexedSeq[T]] {
      def apply(from: String) = immutable.IndexedSeq.newBuilder[T]
      def apply() = immutable.IndexedSeq.newBuilder[T]
    }

你的意思是,你的 Iterable 不是字符串?

trait CanBuildFrom[-From, -Elem, +To]

这就是推断 Nothing 或 Any 的坏处。

编辑:对不起,我说错了,我看到你没有明确地告诉它。

更新:

由于 CBF 在 From 中是逆变的,因此来自 String 的 CBF 用作来自 Nothing 的 CBF。

scala> typeOf[CanBuildFrom[Nothing,Int,Seq[Int]]] <:< typeOf[CanBuildFrom[String,Int,Seq[Int]]]
res0: Boolean = false

scala> typeOf[CanBuildFrom[String,Int,Seq[Int]]] <:< typeOf[CanBuildFrom[Nothing,Int,Seq[Int]]]
res1: Boolean = true

例如,如果您需要从 immutable.Map 构建,您需要来自 collection.Map 的 CBF 才能工作。

正如其他人评论的那样,Nothing 很奇怪。但是你得到了你所要求的。也就是说,您未指定,这意味着您不太介意返回什么,Vector 或其他什么。

【讨论】:

  • 我很感激你的努力,但我就是不明白你想表达什么。它是否说生成的IndexedSeq 是由fallbackStringCanBuildFrom 创建的?顺便说一句,我确实了解了 -Yinfer-debug,以了解 CBF 周围的魔力。
  • @Beryllium 是的!这对一些聪明人来说可能很明显,但对我来说不是。
【解决方案4】:

我同意这很奇怪。你为什么不直接使用Vector.newBuilder,如果那是你要找的?

scala> val sb = Vector.newBuilder[Int]
sb: scala.collection.mutable.Builder[Int,scala.collection.immutable.Vector[Int]] = scala.collection.immutable.VectorBuilder@1fb7482a

scala> println(sb.getClass.getName)
scala.collection.immutable.VectorBuilder

scala> sb += 1
res1: sb.type = scala.collection.immutable.VectorBuilder@1fb7482a

scala> sb += 2
res2: sb.type = scala.collection.immutable.VectorBuilder@1fb7482a

scala> val s = sb.result()
s: scala.collection.immutable.Vector[Int] = Vector(1, 2)

scala> println(s.getClass.getName)
scala.collection.immutable.Vector

【讨论】:

  • 我只是在寻找SeqIterable 有订单),仅此而已。这就是为什么我想把挑选最好的Builder 和实现类的任务委托给一般的伴生对象。可能我的问题不够清楚,VectorBuilder 只是可能替代方案的一个示例。
猜你喜欢
  • 2017-06-12
  • 2011-12-17
  • 2012-11-12
  • 2017-01-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多