但在这种情况下,我无法使用不变性
让我们选择一个函数,它需要 2 Map
并返回包含 ma
和 mb
中每个元素的 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
地图
让我们假设 ma
和 mb
是 scala.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) { _ + _ }
}
在这种情况下,我们的结果是从 ma
(zero
)开始的累积值。
中间结果
显然,这种不可改变的解决方案是在折叠时产生和销毁许多 Map
实例,但值得一提的是,这些实例并不是已经累积的 Map
的完整克隆,而是与现有实例共享重要的结构(数据)。
更容易合理
如果它更像是 .foldLeft
方法的声明,那么更容易推理语义。使用不可变数据结构有助于使我们的实现更容易推理。