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