Scala Language
Werken met gegevens in onveranderlijke stijl
Zoeken…
Opmerkingen
Waarde- en variabelenamen moeten in het onderste kameelgeval staan
Namen van constanten moeten in hoofdletters zijn. Dat wil zeggen, als het lid definitief is, onveranderlijk en het behoort tot een pakketobject of een object, kan het als een constante worden beschouwd
Naam van methode, waarde en variabele moet in het onderste kameelgeval staan
Bron: http://docs.scala-lang.org/style/naming-conventions.html
Deze compilatie:
val (a,b) = (1,2)
// a: Int = 1
// b: Int = 2
maar dit doet niet:
val (A,B) = (1,2)
// error: not found: value A
// error: not found: value B
Het is niet alleen val vs. var
val
en var
scala> val a = 123
a: Int = 123
scala> a = 456
<console>:8: error: reassignment to val
a = 456
scala> var b = 123
b: Int = 123
scala> b = 321
b: Int = 321
-
val
verwijzingen zijn onveranderlijk: net als eenfinal
variabele inJava
, kunt u deze niet meer wijzigen nadat deze is geïnitialiseerd -
var
referenties kunnen opnieuw worden toegewezen als een eenvoudige variabele-declaratie in Java
Onveranderlijke en veranderlijke collecties
val mut = scala.collection.mutable.Map.empty[String, Int]
mut += ("123" -> 123)
mut += ("456" -> 456)
mut += ("789" -> 789)
val imm = scala.collection.immutable.Map.empty[String, Int]
imm + ("123" -> 123)
imm + ("456" -> 456)
imm + ("789" -> 789)
scala> mut
Map(123 -> 123, 456 -> 456, 789 -> 789)
scala> imm
Map()
scala> imm + ("123" -> 123) + ("456" -> 456) + ("789" -> 789)
Map(123 -> 123, 456 -> 456, 789 -> 789)
De standaardbibliotheek van Scala biedt zowel onveranderlijke als veranderlijke datastructuren, niet de verwijzing ernaar. Elke keer dat een onveranderlijke datastructuur "gemodificeerd" wordt, wordt een nieuwe instantie geproduceerd in plaats van de oorspronkelijke verzameling ter plekke te wijzigen. Elke instantie van de collectie kan een belangrijke structuur delen met een andere instantie.
Mutabele en onveranderlijke verzameling (officiële Scala-documentatie)
Maar ik kan in dit geval geen onveranderlijkheid gebruiken!
Laten we als voorbeeld een functie kiezen die 2 Map
en een Map
retourneren die elk element in ma
en mb
:
def merge2Maps(ma: Map[String, Int], mb: Map[String, Int]): Map[String, Int]
Een eerste poging zou kunnen zijn om de elementen van een van de kaarten te doorlopen met behulp for ((k, v) <- map)
en op de een of andere manier de samengevoegde kaart terug te geven.
def merge2Maps(ma: ..., mb: ...): Map[String, Int] = {
for ((k, v) <- mb) {
???
}
}
Deze eerste zet onmiddellijk voeg een inperken: een mutatie buiten die for
nu nodig is. Dit is duidelijker bij het ontdoen van de for
:
// this:
for ((k, v) <- map) { ??? }
// is equivalent to:
map.foreach { case (k, v) => ??? }
"Waarom moeten we muteren?"
foreach
vertrouwt op bijwerkingen. Elke keer als we iets binnen een foreach
willen laten gebeuren, moeten we "neveneffecten", in dit geval kunnen we een variabel var result
muteren of we kunnen een veranderlijke gegevensstructuur gebruiken.
De result
en vullen
Laten we aannemen dat ma
en mb
scala.collection.immutable.Map
, we kunnen het result
Map van ma
:
val result = mutable.Map() ++ ma
Herhaal vervolgens door mb
de elementen toe te voegen en als de key
van het huidige element op ma
al bestaat, laten we deze vervangen door de mb
key
.
mb.foreach { case (k, v) => result += (k -> v) }
Mutabele implementatie
Tot dusver zo goed, moesten we "veranderlijke collecties gebruiken" en een correcte implementatie zou kunnen zijn:
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
}
Zoals verwacht:
scala> merge2Maps(Map("a" -> 11, "b" -> 12), Map("b" -> 22, "c" -> 23))
Map(a -> 11, b -> 22, c -> 23)
Vouwen te hulp
Hoe kunnen we in dit scenario van foreach
afkomen? Als alles wat we moeten doen in feite de collectie-elementen herhalen en een functie toepassen terwijl het resultaat op optie wordt verzameld, zou .foldLeft
kunnen worden gebruikt:
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) { _ + _ }
}
In dit geval is ons "resultaat" de geaccumuleerde waarde die begint met ma
, de zero
van de .foldLeft
.
Gemiddeld resultaat
Het is duidelijk dat deze onveranderlijke oplossing veel Map
instanties produceert en vernietigt tijdens het vouwen, maar het is vermeldenswaard dat die instanties geen volledige kloon van de verzamelde Map
, maar in plaats daarvan een aanzienlijke structuur (gegevens) delen met de bestaande instantie.
Gemakkelijkere redelijkheid
Het is gemakkelijker om over de semantiek te redeneren als deze meer declaratief is dan de .foldLeft
benadering. Het gebruik van onveranderlijke datastructuren kan helpen onze implementatie eenvoudiger te redeneren.