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

W.P*_*ill 19 scala set immutability sequence scala-collections

如果我打电话toSeq给一个不可变的Set集合,我会得到一个ArrayBuffer.

scala> Set(1,2,3).toSeq // returns Seq[Int] = ArrayBuffer(1, 2, 3)
Run Code Online (Sandbox Code Playgroud)

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

一般来说,我希望Scala操作总是产生不可变的结果,除非我明确请求一个可变的结果.这一直是我的假设,但这里是一个不正确的,我实际上只花了一个小时调试一个问题,其中一个意外的存在ArrayBuffer导致一个match语句中的运行时错误.我的修复是改变Set(...).toSeqSet(...).toList,但这种感觉就像一个黑客,因为有我的应用程序没有什么需要在这一点上特别的列表.

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

这是Scala 2.9.2.

som*_*ytt 12

是关于Seq是否应该表示immutable.Seq的最新线程.

罗兰库恩:

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

可变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)
Run Code Online (Sandbox Code Playgroud)


Owe*_*wen 11

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

() => Seq[Int]
Run Code Online (Sandbox Code Playgroud)

() => ArrayBuffer[Int]
Run Code Online (Sandbox Code Playgroud)

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

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

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

当然,这可能会被滥用:

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
Run Code Online (Sandbox Code Playgroud)

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