但在这种情况下,我无法使用不变性

让我们选择一个函数,它需要 2 Map 并返回包含 mamb 中每个元素的 Map

def merge2Maps(ma: Map[String, Int], mb: Map[String, Int]): Map[String, Int]

第一次尝试可能是使用 for ((k, v) <- map) 迭代其中一个地图的元素,并以某种方式返回合并的地图。

def merge2Maps(ma: ..., mb: ...): Map[String, Int] = {

  for ((k, v) <- mb) {
    ???
  }

}

这第一步立即增加了一个约束: 现在*需要*for 之外的突变。当脱糖时,这一点更清楚:

// this:
for ((k, v) <- map) { ??? }

// is equivalent to:
map.foreach { case (k, v) => ??? }

“为什么我们要改变?”

foreach 依赖于副作用。每当我们想要在 foreach 中发生某些事情时,我们需要副作用,在这种情况下,我们可以改变变量 var result 或者我们可以使用可变数据结构。

创建和填充 result 地图

让我们假设 mambscala.collection.immutable.Map,我们可以从 ma 创建 result 地图:

val result = mutable.Map() ++ ma

然后迭代通过 mb 添加它的元素,如果 ma 上当前元素的 key 已经存在,让我们用 mb 覆盖它。

mb.foreach { case (k, v) => result += (k -> v) }

可变实施

到目前为止,我们必须使用可变集合,正确的实现可能是:

def merge2Maps(ma: Map[String, Int], mb: Map[String, Int]): Map[String, Int] = {
  val result = scala.collection.mutable.Map() ++ ma
  mb.foreach { case (k, v) => result += (k -> v) }
  result.toMap // to get back an immutable Map
}

正如所料:

scala> merge2Maps(Map("a" -> 11, "b" -> 12), Map("b" -> 22, "c" -> 23))
  Map(a -> 11, b -> 22, c -> 23)

折叠救援

在这种情况下我们如何摆脱 foreach?如果我们要做的就是基本遍历集合元素并应用函数,同时在选项上累积结果可能是使用 .foldLeft

def merge2Maps(ma: Map[String, Int], mb: Map[String, Int]): Map[String, Int] = {
  mb.foldLeft(ma) { case (result, (k, v)) => result + (k -> v) }
  // or more concisely mb.foldLeft(ma) { _ + _ }
}

在这种情况下,我们的结果是从 mazero)开始的累积值。

中间结果

显然,这种不可改变的解决方案是在折叠时产生和销毁许多 Map 实例,但值得一提的是,这些实例并不是已经累积的 Map 的完整克隆,而是与现有实例共享重要的结构(数据)。

更容易合理

如果它更像是 .foldLeft 方法的声明,那么更容易推理语义。使用不可变数据结构有助于使我们的实现更容易推理。