Scala不可变映射,何时变?

Scala不可变映射,何时变?,scala,map,immutability,mutable,use-case,Scala,Map,Immutability,Mutable,Use Case,我目前的用例非常简单,可变映射或不可变映射都可以做到这一点 有一个接受不可变映射的方法,该方法调用一个同样接受不可变映射的第三方API方法 def doFoo(foo: String = "default", params: Map[String, Any] = Map()) { val newMap = if(someCondition) params + ("foo" -> foo) else params api.doSomething(newMap) } 所

我目前的用例非常简单,可变映射或不可变映射都可以做到这一点

有一个接受不可变映射的方法,该方法调用一个同样接受不可变映射的第三方API方法

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

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

  api.doSomething(newMap)
}
所讨论的映射通常非常小,最多可能有一个嵌入的case类实例列表,最多有几千个条目。因此,同样,在这种情况下,假设不可变的影响很小(即通过newMap val副本基本上有两个映射实例)

不过,复制地图只是为了得到一个新地图,上面有几个k->v条目,这让我有点烦

对于我想要添加的条目,我可以使用mutable和
params.put(“bar”,bar)
,等等,然后
params.toMap
转换为不可变的api调用,这是一个选项。但是我必须导入和传递可变映射,这与使用Scala的默认不变映射相比有点麻烦

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

谢谢

编辑
因此,在不可变映射上的添加操作似乎需要几乎恒定的时间,证实了@dhg和@Nicolas关于未进行完整复制的断言,这就解决了所呈现的具体案例的问题。

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

这种行为在
列表中最容易看到。如果我有一个
vala=List(1,2,3)
,那么该列表存储在内存中。但是,如果我预先添加了一个额外的元素,比如
valb=0::a
,我确实会得到一个新的4元素
列表
,但是Scala没有复制原始列表
a
。相反,我们只是创建了一个名为
b
的新链接,并给它一个指向现有列表
a
的指针


您也可以为其他类型的收藏设想类似的策略。例如,如果我将一个元素添加到一个
映射
,那么集合可以简单地包装现有映射,在需要时返回到它,同时提供一个API,就像它是一个单一的
映射

除了dhg的答案之外,您还可以查看。如果添加/删除操作不需要线性时间,那么它必须执行其他操作,而不仅仅是简单地复制整个结构。(请注意,相反的说法并不正确:这不是因为复制整个结构需要线性时间)

使用可变对象本身并不坏,但在函数式编程环境中会变得不好,在函数式编程环境中,您试图通过保持函数纯质和对象不变来避免副作用

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

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
}

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

我喜欢使用collections.maps作为声明的参数类型(输入或返回值),而不是可变或不可变的映射。集合映射是不可变的接口,适用于这两种类型的实现。使用映射的使用者方法实际上不需要知道映射实现或它是如何构造的。(无论如何,这真的不关它的事)

如果你对使用地图的消费者隐藏地图的特定结构(无论是可变的还是不可变的),那么你仍然会得到一个本质上不可变的地图。通过使用collection.Map作为不可变接口,您完全消除了消费者编写为使用不可变的.Map类型的对象时所存在的所有“.toMap”低效。必须将一个完全构造好的映射转换成另一个映射,只是为了遵从第一个映射不支持的接口,这在您考虑它时是绝对不必要的开销


我猜想在几年后,我们会回顾三组独立的接口(可变映射、不可变映射和集合映射),并意识到99%的时间实际上只需要两个接口(可变映射和集合),并且使用(不幸的是)默认不可变映射接口确实会为可伸缩语言“

+1,很酷,如果是这样的话,这不是问题。当只需要在原始MAPI中添加几个k->v时,一想到制作完整的副本就很恼火。详细说明哪些不可变的集合在添加时更健壮,哪些不健壮会很有趣。我希望ListMaps不会非常健壮。+1,非常好的链接,谢谢。根据Performance&Characteristics,在不可变HashMap上执行add操作需要的时间几乎是恒定的,在当前情况下,这正是我想要听到的;-)是的,使用不可变默认值的替代方法是传入一个可变映射,然后在进行api调用之前转换为不可变映射,因此与您的“全部在本地完成”方法相比,功能上并不纯粹。听起来,对于一般的、非极端的情况,不可变默认值是可以的。当然,我仍然不清楚可变映射在不变映射上的具体用例。不提供导致我提出这个问题的琐碎案例,应该让大门敞开着……+1:这基本上是Microsoft的BCL immutable collections()for.NET所采用的“构建器”模式。请参见。