Scala不可变映射,何时可变?

vir*_*yes 23 scala mutable map use-case immutability

我目前的用例非常简单,无论是可变的还是不可变的Map都可以解决问题.

有一个采用不可变Map的方法,然后调用第三方API方法,该方法也采用不可变Map

def doFoo(foo: String = "default", params: Map[String, Any] = Map()) {

  val newMap = 
    if(someCondition) params + ("foo" -> foo) else params

  api.doSomething(newMap)
}
Run Code Online (Sandbox Code Playgroud)

有问题的地图通常很小,最多可能有一个嵌入的案例类实例列表,最多几千个条目.因此,再次假设在这种情况下对于不可变的影响很小(即通过newMap val副本基本上有2个Map实例).

不过,它让我有点唠叨,复制地图只是为了得到一张新的地图,上面贴着几个k-> v条目.

我可以params.put("bar", bar)为我想要处理的条目变为可变等等,然后params.toMap为api调用转换为immutable,这是一个选项.但后来我必须导入并传递可变映射,与使用Scala的默认不可变映射相比,这有点麻烦.

那么,对于在不可变映射上使用可变映射的合理/良好实践的一般指导原则是什么?

谢谢

编辑 所以,看起来不可变地图上的添加操作接近恒定时间,确认@ dhg和@Nicolas断言没有制作完整副本,这解决了所呈现的具体案例的问题.

dhg*_*dhg 36

根据不可变的Map实现,添加一些条目可能实际上不会复制整个原始Map.这是不可变数据结构方法的优势之一:Scala将尽可能少地尝试复制.

这种行为最容易看到List.如果我有val a = List(1,2,3),那么该列表存储在内存中.但是,如果我在前面加上一个额外的元素一样val b = 0 :: a,我得到一个新的4元List回来,但斯卡拉并没有复制一部开拓创新的列表a.相反,我们只创建了一个名为它的新链接,b并为其指定了现有List a.

您也可以为其他类型的集合设想这样的策略.例如,如果我向a添加一个元素Map,则集合可以简单地包装现有映射,在需要时回退到它,同时提供API就好像它是单个映射一样Map.

  • +1,很酷,如果是这样的话,这不是问题。当只需要在原始地图中添加几个 k->v 时,对制作完整副本的想法感到恼火 (2认同)

par*_*tic 14

使用可变对象本身并不坏,它在函数式编程环境中变得很糟糕,在这种环境中,你试图通过保持函数纯和对象不可变来避免副作用.

但是,如果在函数内创建可变对象并修改此对象,则如果不在函数外部释放对该对象的引用,则该函数仍然是纯的.可以使用以下代码:

def buildVector( x: Double, y: Double, z: Double ): Vector[Double] = {
    val ary = Array.ofDim[Double]( 3 )
    ary( 0 ) = x
    ary( 1 ) = y
    ary( 2 ) = z
    ary.toVector
}
Run Code Online (Sandbox Code Playgroud)

现在,我认为这种方法在两种情况下是有用/推荐的:(1)性能,如果创建和修改不可变对象是整个应用程序的瓶颈; (2)代码可读性,因为有时更容易修改复杂的对象(而不是诉诸镜头,拉链等)


Nic*_*las 5

除了dhg的答案,您还可以查看scala集合性能.如果添加/删除操作不需要线性时间,则必须执行其他操作,而不仅仅是复制整个结构.(注意反过来说不是这样的:它不是因为复制整个结构需要线性时间)