val-mutable versus var-inmutable en Scala

Respuestas:

104

Esta es una pregunta bastante común. Lo difícil es encontrar los duplicados.

Debes luchar por la transparencia referencial . Lo que eso significa es que, si tengo una expresión "e", podría hacer una val x = ey reemplazarla epor x. Esta es la propiedad que rompe la mutabilidad. Siempre que necesite tomar una decisión de diseño, maximice la transparencia referencial.

En la práctica, un método local vares el más seguro varque existe, ya que no escapa al método. Si el método es corto, mejor aún. 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 cosas 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 tanto, romperá la transparencia referencial. Pero tener una colección mutable significa que incluso el objeto en sí mismo podría perder el control de quién lo está cambiando.

Daniel C. Sobral
fuente
38
Bonito, nuevo cuadro grande en mi mente: Prefiero immutable valdurante immutable varmás de mutable valmás mutable var. ¡Especialmente immutable varterminado mutable val!
Peter Schmitz
2
Tenga en cuenta que aún podría cerrar (como en la filtración de una "función" de efectos secundarios que puede cambiarla) sobre un mutable local var. Otra característica interesante del uso de colecciones inmutables es que puedes mantener copias viejas de manera eficiente, incluso si las varmutaciones.
Dan misterioso
1
tl; dr: prefiero var x: Set[Int] sobre val x: mutable.Set[Int] ya que si pasa xa alguna otra función, en el primer caso, está seguro, esa función no puede mutar xpor usted.
Pathikrit
17

Si trabaja con colecciones inmutables y necesita "modificarlas", por ejemplo, agregarles elementos en un bucle, entonces debe usar vars porque necesita almacenar la colección resultante en algún lugar. Si solo lee de colecciones inmutables, use vals.

En general, asegúrese de no confundir referencias y objetos. vals son referencias inmutables (punteros constantes en C). Es decir, cuando lo use val x = new MutableFoo(), podrá cambiar el objeto al que xapunta, pero no podrá cambiar a qué objeto x apunta. Lo contrario es válido si usa var x = new ImmutableFoo(). Siguiendo mi consejo inicial: si no necesita cambiar a qué objeto apunta un punto de referencia, use vals.

Malte Schwerhoff
fuente
1
var immutable = something(); immutable = immutable.update(x)frustra el propósito de usar una colección inmutable. Ya ha renunciado a la transparencia referencial y, por lo general, puede obtener el mismo efecto de una colección mutable con una mayor complejidad de tiempo. De las cuatro posibilidades ( valy varmutable e inmutable), esta tiene menos sentido. Yo uso a menudo val mutable.
Jim Pivarski
3
@JimPivarski No estoy de acuerdo, al igual que otros, vea la respuesta de Daniel y el comentario de Peter. Si necesita actualizar una estructura de datos, entonces usar una var inmutable en lugar de un val mutable tiene la ventaja de que puede filtrar referencias a la estructura sin correr el riesgo de que otros la modifiquen de una manera que rompa sus suposiciones locales. La desventaja de estos "otros" es que pueden leer datos obsoletos.
Malte Schwerhoff
He cambiado de opinión y estoy de acuerdo contigo (dejo mi comentario original para la historia). Desde entonces he usado esto, especialmente en var list: List[X] = Nil; list = item :: list; ...y había olvidado que una vez escribí de manera diferente.
Jim Pivarski
@MalteSchwerhoff: "datos obsoletos" es realmente deseable, dependiendo de cómo haya diseñado su programa, si la coherencia es crucial; este es, por ejemplo, uno de los principales principios subyacentes en cómo funciona la concurrencia en Clojure.
Erik Kaplun
@ErikAllik No diría que los datos obsoletos sean deseables per se, pero estoy de acuerdo en que pueden estar perfectamente bien, dependiendo de las garantías que quieras / necesites dar a tus clientes. ¿O tiene un ejemplo en el que el solo hecho de leer datos obsoletos es realmente una ventaja? No me refiero a las consecuencias de aceptar datos obsoletos, que podrían ser un mejor rendimiento o una API más simple.
Malte Schwerhoff
9

La mejor forma de responder a esto es con un ejemplo. Supongamos que tenemos algún proceso que simplemente recopila 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 recopilando 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. Ojalá puedas ver a dónde va esto.

Si almacenamos esta colección en un mutable val, (mutable porque estamos agregando continuamente), esto significa que el proceso que realiza el registro buscará el mismo objeto que aún está siendo actualizado por nuestro proceso de recolección. Esa colección puede actualizarse en cualquier momento, por lo que cuando llegue el momento de iniciar sesión, es posible que no estemos registrando la colección que enviamos.

Si usamos un inmutable var, enviamos una estructura de datos inmutable al registrador. Cuando agreguemos más números a nuestra colección, reemplazaremos nuestro varcon una nueva estructura de datos inmutable . ¡Esto no significa que la colección enviada al registrador sea reemplazada! Todavía hace referencia a la colección que se envió. Entonces nuestro registrador registrará la colección que recibió.

jmazin
fuente
1

Creo que los ejemplos de 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 escenarios de concurrencia: la importancia de la inmutabilidad para la concurrencia . Y ya que estamos en eso, tenga en cuenta el uso preferido de sincronizado frente a @ volátil frente a algo como AtomicReference: tres herramientas

Bogdan Nicolau
fuente
-2

var immutable vs. val mutable

Además de muchas excelentes respuestas a esta pregunta. A continuación, se muestra un ejemplo sencillo 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 no se permite la reasignación.

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 no se permite la reasignación:

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

Dado que los parámetros de función se tratan como valesta reasignación no está permitida.

Akavall
fuente
Esto es incorrecto. La razón por la que obtuvo ese error es porque usó Vector en su segundo ejemplo que, de forma predeterminada, es inmutable. Si usa un ArrayBuffer, verá que se compila bien y hace lo mismo donde simplemente agrega el nuevo elemento e imprime el búfer mutado. pastebin.com/vfq7ytaD
EdgeCaseBerg
@EdgeCaseBerg, estoy usando intencionalmente un Vector en mi segundo ejemplo, porque estoy tratando de mostrar que el comportamiento del primer ejemplo mutable valno es posible con immutable var. ¿Qué es incorrecto aquí?
Akavall
Estás comparando manzanas con naranjas aquí. Vector no tiene un +=método como el búfer de matriz. Tus respuestas implican que +=es lo mismo x = x + yque lo que no es. Su declaración de que los parámetros de función se tratan como vals es correcta y obtiene el error que menciona, pero solo porque usó =. Puede obtener el mismo error con un ArrayBuffer, por lo que la mutabilidad de las colecciones aquí no es realmente relevante. Así que no es una buena respuesta porque no llega a lo que está hablando el OP. Aunque es un buen ejemplo de los peligros de pasar una colección mutable si no tenía la intención de hacerlo.
EdgeCaseBerg
@EdgeCaseBerg Pero no puedes replicar el comportamiento con el que obtengo ArrayBuffer, usando Vector. La pregunta del OP es amplia, pero estaban buscando sugerencias sobre cuándo usar cuál, así que creo que mi respuesta es útil porque ilustra los peligros de pasar una colección mutable (el hecho de que valno ayude); immutable vares más seguro que mutable val.
Akavall