Scala Language
Travailler avec des données dans un style immuable
Recherche…
Remarques
Les noms de valeur et de variable doivent être en cas de chameau inférieur
Les noms constants doivent être en cas de chameau supérieur. Autrement dit, si le membre est final, immuable et qu’il appartient à un objet ou à un objet, il peut être considéré comme une constante
La méthode, la valeur et les noms de variables doivent être dans le cas camel inférieur
Source: http://docs.scala-lang.org/style/naming-conventions.html
Cette compilation:
val (a,b) = (1,2)
// a: Int = 1
// b: Int = 2
mais ce n'est pas le cas:
val (A,B) = (1,2)
// error: not found: value A
// error: not found: value B
Ce n'est pas juste val vs var
val
et 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
référencesval
sont inchangeables: comme une variablefinal
enJava
, une fois initialisée, vous ne pouvez plus la changer -
var
référencesvar
peuvent être réaffectées en tant que déclaration de variable simple en Java
Collections immuables et Mutable
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)
La bibliothèque standard Scala offre à la fois des structures de données immuables et mutables, et non la référence à celle-ci. Chaque fois qu'une structure de données immuable est "modifiée", une nouvelle instance est produite au lieu de modifier la collection d'origine sur place. Chaque instance de la collection peut partager une structure significative avec une autre instance.
Collection Mutable et Immuable (Documentation Scala Officielle)
Mais je ne peux pas utiliser l'immuabilité dans ce cas!
Prenons comme exemple une fonction qui prend 2 Map
et renvoie une Map
contenant tous les éléments de ma
et mb
:
def merge2Maps(ma: Map[String, Int], mb: Map[String, Int]): Map[String, Int]
Une première tentative pourrait être une itération à travers les éléments d'une des cartes en utilisant for ((k, v) <- map)
et renvoyer d'une manière ou d'une autre la carte fusionnée.
def merge2Maps(ma: ..., mb: ...): Map[String, Int] = {
for ((k, v) <- mb) {
???
}
}
Ce tout premier pas ajoute immédiatement une contrainte: une mutation en dehors de celle for
est maintenant nécessaire . Ceci est plus clair lors de la suppression du sucre for
:
// this:
for ((k, v) <- map) { ??? }
// is equivalent to:
map.foreach { case (k, v) => ??? }
"Pourquoi devons-nous muter?"
foreach
repose sur les effets secondaires. Chaque fois que nous voulons que quelque chose se produise dans un foreach
nous devons "modifier quelque chose", dans ce cas nous pourrions muter un var result
variable ou utiliser une structure de données mutable.
Créer et remplir la carte de result
Supposons que ma
et mb
sont scala.collection.immutable.Map
, nous pourrions créer le result
Map de ma
:
val result = mutable.Map() ++ ma
Ensuite, parcourez mb
ajoutant ses éléments et si la key
de l'élément actuel sur ma
existe déjà, passons à celle de l'élément mb
.
mb.foreach { case (k, v) => result += (k -> v) }
Implémentation Mutable
Jusqu'ici tout va bien, nous avons "dû utiliser des collections mutables" et une implémentation correcte pourrait être:
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
}
Comme prévu:
scala> merge2Maps(Map("a" -> 11, "b" -> 12), Map("b" -> 22, "c" -> 23))
Map(a -> 11, b -> 22, c -> 23)
Se plier à la rescousse
Comment pouvons-nous nous débarrasser de foreach
dans ce scénario? Si tout ce que nous faisons est essentiellement itératif sur les éléments de la collection et appliquer une fonction tout en accumulant le résultat, l'option pourrait utiliser .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) { _ + _ }
}
Dans ce cas, notre "résultat" est la valeur accumulée à partir de ma
, le zero
du .foldLeft
.
Résultat intermédiaire
Evidemment, cette solution immuable produit et détruit de nombreuses instances de la Map
lors du pliage, mais il convient de mentionner que ces instances ne sont pas un clone complet de la Map
accumulée mais partagent une structure (données) importante avec l'instance existante.
Facilité de raisonnement
Il est plus facile de raisonner sur la sémantique si elle est plus déclarative que l’approche .foldLeft
. L'utilisation de structures de données immuables pourrait contribuer à simplifier notre implémentation.