19

如果我调用toSeq一个不可变的Set集合,我会得到一个ArrayBuffer.

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

这让我很惊讶。鉴于 Scala 强调使用不可变数据结构,我希望得到一个不可变序列,例如 a VectororList而不是 mutable ArrayBuffer。set 元素的返回顺序当然应该是未定义的,但似乎没有任何语义上的原因说明该顺序也应该是可变的。

一般来说,我希望 Scala 操作总是产生不可变的结果,除非我明确地请求一个可变的结果。这一直是我的假设,但这里的假设不正确,实际上我只花了一个小时调试一个问题,即意外存在ArrayBuffer导致match语句中出现运行时错误。我的解决方法是更改Set(...).toSeq​​为Set(...).toList,但这感觉像是一个 hack,因为那时我的应用程序没有什么特别需要列表的地方。

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

这是 Scala 2.9.2。

4

2 回答 2

12

是最近关于 Seq 是否应该意味着 immutable.Seq 的线程。

罗兰·库恩:

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

可变可变参数的例子相当狡猾。

最近,

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)
于 2012-12-04T05:45:32.950 回答
11

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

() => Seq[Int]

不是

() => ArrayBuffer[Int]

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

所以,即使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

但是,好吧,很多事情也可以。

于 2012-12-04T05:18:10.933 回答