首页 文章

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

提问于
浏览
18

如果我在一个不可变的 Set 集合上调用 toSeq ,我得到 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 回答

  • 11

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

    罗兰库恩:

    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)
    
  • 10

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

    () => Seq[Int]
    

    () => ArrayBuffer[Int]
    

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

    所以,即使 toSeq 给你一个可变对象,你实际上也不能改变它(没有强制转换或模式匹配到 ArrayBuffer - 所以真正的"strange"部分是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
    

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

相关问题