Scala Language
Lavorare con i dati in uno stile immutabile
Ricerca…
Osservazioni
Il valore e i nomi delle variabili dovrebbero essere nel caso di cammello inferiore
I nomi costanti dovrebbero essere nella custodia del cammello superiore. Cioè, se il membro è definitivo, immutabile e appartiene a un oggetto pacchetto o un oggetto, può essere considerato una costante
Il metodo, il valore e i nomi delle variabili dovrebbero essere in un caso di cammello inferiore
Fonte: http://docs.scala-lang.org/style/naming-conventions.html
Questo compila:
val (a,b) = (1,2)
// a: Int = 1
// b: Int = 2
ma questo non:
val (A,B) = (1,2)
// error: not found: value A
// error: not found: value B
Non è solo val vs. var
val
e 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
riferimentival
sono immutabili: come una variabilefinal
inJava
, una volta inizializzata non puoi cambiarla -
var
riferimentivar
sono riassegnabili come una semplice dichiarazione di variabile in Java
Collezioni immutabili e mutevoli
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 libreria standard di Scala offre sia strutture di dati immutabili che mutevoli, non il riferimento ad esso. Ogni volta che una struttura di dati immutabile viene "modificata", viene prodotta una nuova istanza invece di modificare la raccolta originale sul posto. Ogni istanza della raccolta può condividere una struttura significativa con un'altra istanza.
Collezione Mutevole e Immutabile (Documentazione ufficiale di Scala)
Ma non posso usare l'immutabilità in questo caso!
Prendiamo come esempio una funzione che richiede 2 Map
e restituisce una Map
contenente ogni elemento in ma
e mb
:
def merge2Maps(ma: Map[String, Int], mb: Map[String, Int]): Map[String, Int]
Un primo tentativo potrebbe iterare attraverso gli elementi di una delle mappe usando for ((k, v) <- map)
e in qualche modo restituire la mappa unita.
def merge2Maps(ma: ..., mb: ...): Map[String, Int] = {
for ((k, v) <- mb) {
???
}
}
Questa primissima mossa aggiunge immediatamente un vincolo: una mutazione al di fuori di quella for
ora è necessario . Questo è più chiaro quando si deseleziona il for
:
// this:
for ((k, v) <- map) { ??? }
// is equivalent to:
map.foreach { case (k, v) => ??? }
"Perché dobbiamo mutare?"
foreach
si basa su effetti collaterali. Ogni volta che vogliamo che qualcosa succeda all'interno di un foreach
abbiamo bisogno di "side-effect something", in questo caso potremmo mutare un var result
variabile var result
o possiamo usare una struttura dati mutabile.
Creazione e compilazione della mappa dei result
Supponiamo che la ma
e mb
siano scala.collection.immutable.Map
, potremmo creare la mappa dei result
da ma
:
val result = mutable.Map() ++ ma
Quindi iterate tramite mb
aggiungendo i suoi elementi e se la key
dell'elemento corrente su ma
esiste già, sovrascriviamo quella di mb
.
mb.foreach { case (k, v) => result += (k -> v) }
Implementazione mutevole
Fin qui tutto bene, "abbiamo dovuto usare collezioni mutevoli" e una corretta implementazione potrebbe essere:
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
}
Come previsto:
scala> merge2Maps(Map("a" -> 11, "b" -> 12), Map("b" -> 22, "c" -> 23))
Map(a -> 11, b -> 22, c -> 23)
Piegando in soccorso
Come possiamo sbarazzarci di foreach
in questo scenario? Se tutto ciò che dobbiamo fare è fondamentalmente scorrere gli elementi della raccolta e applicare una funzione mentre si accumula il risultato sull'opzione potrebbe essere .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 questo caso il nostro "risultato" è il valore accumulato a partire da ma
, lo zero
della .foldLeft
.
Risultato intermedio
Ovviamente questa soluzione immutabile sta producendo e distruggendo molte istanze di Map
durante la piegatura, ma vale la pena ricordare che quelle istanze non sono un clone completo della Map
accumulato ma che invece condividono una struttura significativa (dati) con l'istanza esistente.
Più ragionevole la ragionevolezza
È più facile ragionare sulla semantica se è più dichiarativa come l'approccio .foldLeft
. L'utilizzo di strutture di dati immutabili potrebbe aiutare a rendere più facile ragionare la nostra implementazione.