Scala Language
Mit Daten unveränderlich arbeiten
Suche…
Bemerkungen
Wert- und Variablennamen sollten im unteren Kamelfall stehen
Konstante Namen sollten im oberen Kamel sein. Wenn das Member also final ist, unveränderlich ist und zu einem Paketobjekt oder einem Objekt gehört, kann es als Konstante betrachtet werden
Methoden-, Wert- und Variablennamen sollten in Kleinbuchstaben stehen
Quelle: http://docs.scala-lang.org/style/naming-conventions.html
Dieses kompilieren:
val (a,b) = (1,2)
// a: Int = 1
// b: Int = 2
aber das tut nicht:
val (A,B) = (1,2)
// error: not found: value A
// error: not found: value B
Es ist nicht nur val vs. var
val
und 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
Verweise sind nicht veränderbar: Wie einefinal
Variable inJava
kann sie nach der Initialisierung nicht mehr geändert werden -
var
Referenzen können als einfache Variablendeklaration in Java erneut zugewiesen werden
Unveränderliche und veränderliche Sammlungen
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)
Die Standardbibliothek von Scala bietet sowohl unveränderliche als auch veränderliche Datenstrukturen, nicht den Verweis darauf. Jedes Mal, wenn eine unveränderliche Datenstruktur "geändert" wird, wird eine neue Instanz erstellt, anstatt die ursprüngliche Sammlung direkt zu ändern. Jede Instanz der Sammlung kann eine signifikante Struktur mit einer anderen Instanz gemeinsam nutzen.
Veränderliche und unveränderliche Sammlung (offizielle Scala-Dokumentation)
Aber ich kann in diesem Fall keine Unveränderlichkeit verwenden!
Nehmen wir als Beispiel eine Funktion, die 2 Map
und gibt eine Map
die jedes Element in ma
und mb
:
def merge2Maps(ma: Map[String, Int], mb: Map[String, Int]): Map[String, Int]
Ein erster Versuch könnte darin bestehen, die Elemente einer der Karten mit for ((k, v) <- map)
zu durchlaufen und die zusammengeführte Karte irgendwie zurückzugeben.
def merge2Maps(ma: ..., mb: ...): Map[String, Int] = {
for ((k, v) <- mb) {
???
}
}
Dieser allererste Schritt fügt sofort eine Einschränkung hinzu: Jetzt wird eine Mutation außerhalb von for
benötigt . Dies ist deutlicher beim Entzuckern for
:
// this:
for ((k, v) <- map) { ??? }
// is equivalent to:
map.foreach { case (k, v) => ??? }
"Warum müssen wir mutieren?"
foreach
stützt sich auf Nebenwirkungen. Jedes Mal, wenn wir möchten, dass etwas innerhalb eines foreach
geschieht, müssen wir etwas "Nebeneffekt" haben. In diesem Fall könnten wir ein variables var result
ändern oder eine veränderliche Datenstruktur verwenden.
Erstellen und Füllen der result
Nehmen wir an, ma
und mb
sind scala.collection.immutable.Map
, wir könnten das result
Map aus ma
erstellen:
val result = mutable.Map() ++ ma
Iterieren dann durch mb
seine Elemente hinzuzufügen und wenn der key
des aktuellen Elements auf ma
bereits vorhanden ist , machen sie es mit dem außer Kraft setzen mb
ein.
mb.foreach { case (k, v) => result += (k -> v) }
Veränderliche Implementierung
So weit so gut, wir mussten "veränderliche Sammlungen verwenden" und eine korrekte Implementierung könnte sein:
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
}
Wie erwartet:
scala> merge2Maps(Map("a" -> 11, "b" -> 12), Map("b" -> 22, "c" -> 23))
Map(a -> 11, b -> 22, c -> 23)
Zur Rettung falten
Wie können wir in diesem Szenario foreach
loswerden? Wenn wir im Grunde nur die Collection-Elemente .foldLeft
und eine Funktion anwenden, während das Ergebnis mit der Option akkumuliert wird, könnte .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) { _ + _ }
}
In diesem Fall ist unser "Ergebnis" der akkumulierte Wert ausgehend von ma
, der zero
von .foldLeft
.
Zwischenergebnis
Offensichtlich produziert und zerstört diese unveränderliche Lösung viele Map
Instanzen beim Falten, aber es ist erwähnenswert, dass diese Instanzen kein vollständiger Klon der Map
, sondern eine signifikante Struktur (Daten) mit der vorhandenen Instanz gemeinsam nutzen.
Einfachere Angemessenheit
Es ist einfacher, über die Semantik zu .foldLeft
wenn sie deklarativer ist als der .foldLeft
Ansatz. Die Verwendung unveränderlicher Datenstrukturen könnte dazu beitragen, die Implementierung der Implementierung zu erleichtern.