Scala Language
Trabajando con datos en estilo inmutable.
Buscar..
Observaciones
Los nombres de valores y variables deben estar en la caja inferior del camello
Los nombres constantes deben estar en la caja superior del camello. Es decir, si el miembro es definitivo, inmutable y pertenece a un objeto de paquete o un objeto, puede considerarse una constante
El método, el valor y los nombres de las variables deben estar en la caja inferior del camello
Fuente: http://docs.scala-lang.org/style/naming-conventions.html
Esta compilación:
val (a,b) = (1,2)
// a: Int = 1
// b: Int = 2
pero esto no lo hace
val (A,B) = (1,2)
// error: not found: value A
// error: not found: value B
No es solo val vs. var.
val
y 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
referenciasval
son inmutables: como una variablefinal
enJava
, una vez que se ha inicializado no se puede cambiar -
var
referenciasvar
son reasignables como una simple declaración de variable en Java
Colecciones inmutables y mutables.
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 biblioteca estándar de Scala ofrece estructuras de datos inmutables y mutables, no la referencia a ella. Cada vez que una estructura de datos inmutables se "modifica", se produce una nueva instancia en lugar de modificar la colección original en el lugar. Cada instancia de la colección puede compartir una estructura significativa con otra instancia.
Colección mutable e inmutable (Documentación Oficial Scala)
¡Pero no puedo usar la inmutabilidad en este caso!
Tomemos como ejemplo una función que toma 2 Map
y devolvemos un Map
contiene cada elemento en ma
y mb
:
def merge2Maps(ma: Map[String, Int], mb: Map[String, Int]): Map[String, Int]
Un primer intento podría ser iterar a través de los elementos de uno de los mapas usando for ((k, v) <- map)
y de alguna manera devolver el mapa combinado.
def merge2Maps(ma: ..., mb: ...): Map[String, Int] = {
for ((k, v) <- mb) {
???
}
}
Este primer movimiento agrega inmediatamente una restricción: ahora se necesita una mutación fuera de la misma for
. Esto es más claro cuando se elimina el azúcar for
:
// this:
for ((k, v) <- map) { ??? }
// is equivalent to:
map.foreach { case (k, v) => ??? }
"¿Por qué tenemos que mutar?"
foreach
basa en los efectos secundarios. Cada vez que queremos que algo suceda dentro de un foreach
necesitamos "efectos secundarios", en este caso podríamos mutar un var result
variable var result
o podemos usar una estructura de datos mutable.
Creando y rellenando el mapa de result
Supongamos que ma
y mb
son scala.collection.immutable.Map
, podríamos crear el Mapa de result
de ma
:
val result = mutable.Map() ++ ma
Luego itere a través de mb
agregando sus elementos y si la key
del elemento actual en ma
ya existe, anulémosla con mb
one.
mb.foreach { case (k, v) => result += (k -> v) }
Implementación mutable
Hasta ahora todo bien, "tuvimos que usar colecciones mutables" y una implementación correcta podría ser:
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
}
Como se esperaba:
scala> merge2Maps(Map("a" -> 11, "b" -> 12), Map("b" -> 22, "c" -> 23))
Map(a -> 11, b -> 22, c -> 23)
Plegado al rescate.
¿Cómo podemos deshacernos de foreach
en este escenario? Si lo único que debemos hacer es iterar básicamente sobre los elementos de la colección y aplicar una función mientras se acumula el resultado en la opción, podría estar usando .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) { _ + _ }
}
En este caso, nuestro "resultado" es el valor acumulado a partir de ma
, el zero
de .foldLeft
.
Resultado intermedio
Obviamente, esta solución inmutable produce y destruye muchas instancias del Map
mientras se pliega, pero vale la pena mencionar que esas instancias no son un clon completo del Map
acumulado, sino que comparten una estructura significativa (datos) con la instancia existente.
Razonabilidad más fácil
Es más fácil razonar acerca de la semántica si es más declarativo como el enfoque .foldLeft
. El uso de estructuras de datos inmutables podría ayudar a hacer que nuestra implementación sea más fácil de razonar.