但在這種情況下,我無法使用不變性

讓我們選擇一個函式,它需要 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 方法的宣告,那麼更容易推理語義。使用不可變資料結構有助於使我們的實現更容易推理。