val-mutable versus var-inmutable en Scala

¿Hay alguna guía en Scala sobre cuándo usar val con una colección mutable versus usar var con una colección inmutable? ¿O debería apuntar a val con una colección inmutable?

El hecho de que haya ambos tipos de colección me da muchas opciones y, a menudo, no sé cómo tomar esa decisión.

Una pregunta bastante común, esta. Lo difícil es encontrar los duplicados.

Debes esforzarte por la transparencia referencial . Lo que eso significa es que, si tengo una expresión “e”, podría hacer un val x = e , y reemplazar e con x . Esta es la propiedad que rompe la mutabilidad. Siempre que necesite tomar una decisión de diseño, maximice la transparencia referencial.

Como una cuestión práctica, un método var local es la var más segura que existe, ya que no escapa al método. Si el método es corto, incluso mejor. Si no es así, intente reducirlo extrayendo otros métodos.

Por otro lado, una colección mutable tiene el potencial de escapar, incluso si no es así. Al cambiar el código, es posible que desee pasarlo a otros métodos o devolverlo. Ese es el tipo de cosa que rompe la transparencia referencial.

En un objeto (un campo), sucede más o menos lo mismo, pero con consecuencias más nefastas. De cualquier forma, el objeto tendrá estado y, por lo tanto, romperá la transparencia referencial. Pero tener una colección mutable significa que incluso el objeto en sí podría perder el control de quién lo está cambiando.

Si trabaja con colecciones inmutables y necesita “modificarlas”, por ejemplo, agregar elementos a ellas en un bucle, entonces debe usar var s porque necesita almacenar la colección resultante en alguna parte. Si solo lees de colecciones inmutables, entonces usa val s.

En general, asegúrese de no confundir referencias y objetos. val s son referencias inmutables (punteros constantes en C). Es decir, cuando use val x = new MutableFoo() , podrá cambiar el objeto al que x apunta, pero no podrá cambiar a qué objeto x puntos. Lo contrario se aplica si usa var x = new ImmutableFoo() . Recogiendo mi consejo inicial: si no necesita cambiar a qué objeto hace referencia, use val s.

La mejor manera de responder esto es con un ejemplo. Supongamos que tenemos algún proceso simplemente recolectando números por alguna razón. Deseamos registrar estos números y enviaremos la colección a otro proceso para hacer esto.

Por supuesto, todavía estamos recolectando números después de enviar la colección al registrador. Y digamos que hay una sobrecarga en el proceso de registro que retrasa el registro real. Con suerte, puedes ver a dónde va esto.

Si almacenamos esta colección en un valor mutable, (mutable porque lo estamos agregando continuamente), esto significa que el proceso que realiza el registro buscará el mismo objeto que nuestro proceso de recostackción está actualizando. Esa recostackción se puede actualizar en cualquier momento, por lo que cuando sea el momento de iniciar sesión, es posible que no estemos registrando la colección que enviamos.

Si utilizamos una variable inmutable, enviamos una estructura de datos inmutables al registrador. Cuando agreguemos más números a nuestra colección, reemplazaremos nuestra var con una nueva estructura de datos inmutable . ¡Esto no significa que se reemplace la colección enviada al registrador! Sigue haciendo referencia a la colección que se envió. Por lo tanto, nuestro registrador registrará la colección que recibió.

Creo que los ejemplos en esta publicación de blog arrojarán más luz, ya que la cuestión de qué combo usar se vuelve aún más importante en los escenarios de simultaneidad: importancia de la inmutabilidad para la concurrencia . Y mientras estamos en ello, tenga en cuenta el uso preferido de vs @ volátil sincronizado vs algo así como AtomicReference: tres herramientas

var immutable vs. val mutable

Además de muchas respuestas excelentes a esta pregunta. Aquí hay un ejemplo simple, que ilustra los peligros potenciales de val mutable :

Los objetos mutables se pueden modificar dentro de los métodos, que los toman como parámetros, mientras que la reasignación no está permitida.

 import scala.collection.mutable.ArrayBuffer object MyObject { def main(args: Array[String]) { val a = ArrayBuffer(1,2,3,4) silly(a) println(a) // a has been modified here } def silly(a: ArrayBuffer[Int]): Unit = { a += 10 println(s"length: ${a.length}") } } 

Resultado:

 length: 5 ArrayBuffer(1, 2, 3, 4, 10) 

Algo como esto no puede suceder con var immutable , porque la reasignación no está permitida:

 object MyObject { def main(args: Array[String]) { var v = Vector(1,2,3,4) silly(v) println(v) } def silly(v: Vector[Int]): Unit = { v = v :+ 10 // This line is not valid println(s"length of v: ${v.length}") } } 

Resultados en:

 error: reassignment to val 

Como los parámetros de la función se tratan como val esta reasignación no está permitida.