为什么Scala的toSeq将不可变Set转换为可变ArrayBuffer?

时间:2012-12-04 04:59:13

标签: scala set sequence immutability scala-collections

如果我在不可变toSeq集合上致电Set,我会获得ArrayBuffer

scala> Set(1,2,3).toSeq // returns Seq[Int] = ArrayBuffer(1, 2, 3)

这令我感到惊讶。鉴于Scala强调使用不可变数据结构,我期望得到一个不可变的序列,如VectorList而不是可变的ArrayBuffer。 set元素的返回顺序当然应该是未定义的,但似乎没有任何语义上的原因,为什么这个顺序也应该是可变的。

一般情况下,我希望Scala操作始终产生不可变结果,除非我明确请求可变结果。这一直是我的假设,但这里是不正确的,我实际上只花了一个小时来调试ArrayBuffer的意外存在导致match语句中的运行时错误的问题。我的修复是将Set(...).toSeq更改为Set(...).toList,但这感觉就像是一个黑客,因为我的应用程序并不需要特定的列表。

Set(...).toSeq返回一个可变对象是Scala实现中的一个缺陷,还是有一个我在这里误解的原则?

这是Scala 2.9.2。

2 个答案:

答案 0 :(得分:11)

Here是关于Seq是否应该表示immutable.Seq。

的最新帖子

Roland Kuhn:

  

collection.Seq没有变异器根本不是一个有效的防御!

可变varargs的例子相当狡猾。

最近,

scala> Set(1,2,3)
res0: scala.collection.immutable.Set[Int] = Set(1, 2, 3)

scala> res0.toSeq
res1: Seq[Int] = ArrayBuffer(1, 2, 3)

scala> res0.to[collection.immutable.Seq]
res2: scala.collection.immutable.Seq[Int] = Vector(1, 2, 3)

答案 1 :(得分:10)

我同意这有点奇怪,但我不相信这是一个缺陷。首先,考虑一下:Set.toSeq的编译时类型是

() => Seq[Int]

() => ArrayBuffer[Int]

ArrayBuffer恰好是返回对象的运行时类型(可能是因为Set.toSeq添加到ArrayBuffer然后只返回而没有转换)。

所以,即使toSeq给你一个可变对象,你实际上也不能改变它(没有强制转换或模式匹配到ArrayBuffer - 所以真正的“奇怪”部分是Scala允许您在任意类上进行模式匹配)。 (你必须相信Set没有抓住对象并改变它,但我认为这是一个公平的假设。)

另一种看待它的方法是,可变类型只是比不可变类型更具体。或者,另一种说法是,每个可变对象也可以被视为一个不可变对象:一个不可变对象有一个getter,一个可变对象有一个getter 一个setter - 但它仍然有一个吸气剂。

当然,这可能会被滥用:

val x = new Seq[Int] {
    var n: Int = 0
    def apply(k: Int) = k
    def iterator = {
        n += 1
        (0 to n).iterator
    }
    def length = n
}

x foreach println _
0
1

x foreach println _
0
1
2

但是,很多事情也是如此。