¿Cuál es la diferencia entre una definición var y val en Scala?

301

¿Cuál es la diferencia entre una vary valdefinición en la Scala y ¿por qué la necesidad lenguaje tanto? ¿Por qué elegirías un valsobre varay viceversa?

Derek Mahar
fuente

Respuestas:

331

Como muchos otros han dicho, el objeto asignado a a valno se puede reemplazar, y el objeto asignado a una varlata. Sin embargo, dicho objeto puede tener su estado interno modificado. Por ejemplo:

class A(n: Int) {
  var value = n
}

class B(n: Int) {
  val value = new A(n)
}

object Test {
  def main(args: Array[String]) {
    val x = new B(5)
    x = new B(6) // Doesn't work, because I can't replace the object created on the line above with this new one.
    x.value = new A(6) // Doesn't work, because I can't replace the object assigned to B.value for a new one.
    x.value.value = 6 // Works, because A.value can receive a new object.
  }
}

Entonces, aunque no podemos cambiar el objeto asignado x, podemos cambiar el estado de ese objeto. En la raíz de esto, sin embargo, había un var.

Ahora, la inmutabilidad es algo bueno por muchas razones. Primero, si un objeto no cambia su estado interno, no tiene que preocuparse si alguna otra parte de su código lo está cambiando. Por ejemplo:

x = new B(0)
f(x)
if (x.value.value == 0)
  println("f didn't do anything to x")
else
  println("f did something to x")

Esto se vuelve particularmente importante con los sistemas multiproceso. En un sistema multiproceso, puede suceder lo siguiente:

x = new B(1)
f(x)
if (x.value.value == 1) {
  print(x.value.value) // Can be different than 1!
}

Si usa valexclusivamente, y solo usa estructuras de datos inmutables (es decir, evitar matrices, todo lo que contiene scala.collection.mutable, etc.), puede estar seguro de que esto no sucederá. Es decir, a menos que haya algún código, tal vez incluso un marco, que haga trucos de reflexión; desafortunadamente, la reflexión puede cambiar valores "inmutables".

Esa es una razón, pero hay otra razón para ello. Cuando lo usa var, puede verse tentado a reutilizar el mismo varpara múltiples propósitos. Esto tiene algunos problemas:

  • Será más difícil para las personas que leen el código saber cuál es el valor de una variable en cierta parte del código.
  • Puede olvidarse de reinicializar la variable en alguna ruta de código y terminar pasando valores incorrectos aguas abajo en el código.

En pocas palabras, el uso vales más seguro y conduce a un código más legible.

Podemos, entonces, ir en la otra dirección. Si valeso es mejor, ¿por qué tenerlo var? Bueno, algunos idiomas tomaron esa ruta, pero hay situaciones en las que la mutabilidad mejora mucho el rendimiento.

Por ejemplo, tome un inmutable Queue. Cuando tú enqueueo las dequeuecosas en él, obtienes un nuevo Queueobjeto. Entonces, ¿cómo procesarías todos los artículos que contiene?

Voy a pasar por eso con un ejemplo. Digamos que tiene una cola de dígitos y desea componer un número de ellos. Por ejemplo, si tengo una cola con 2, 1, 3, en ese orden, quiero recuperar el número 213. Primero resolvamos con mutable.Queue:

def toNum(q: scala.collection.mutable.Queue[Int]) = {
  var num = 0
  while (!q.isEmpty) {
    num *= 10
    num += q.dequeue
  }
  num
}

Este código es rápido y fácil de entender. Su principal inconveniente es que la cola que se pasa es modificada por toNum, por lo que debe hacer una copia de antemano. Ese es el tipo de gestión de objetos de la que la inmutabilidad te libera.

Ahora, vamos a convertirlo en un immutable.Queue:

def toNum(q: scala.collection.immutable.Queue[Int]) = {
  def recurse(qr: scala.collection.immutable.Queue[Int], num: Int): Int = {
    if (qr.isEmpty)
      num
    else {
      val (digit, newQ) = qr.dequeue
      recurse(newQ, num * 10 + digit)
    }
  }
  recurse(q, 0)
}

Debido a que no puedo reutilizar alguna variable para realizar un seguimiento de mi num, como en el ejemplo anterior, necesito recurrir a la recursividad. En este caso, es una recursión de cola, que tiene un rendimiento bastante bueno. Pero ese no es siempre el caso: a veces simplemente no hay una buena solución de recursión de cola (legible, simple).

Tenga en cuenta, sin embargo, que puedo reescribir ese código para usar una immutable.Queuey una varal mismo tiempo. Por ejemplo:

def toNum(q: scala.collection.immutable.Queue[Int]) = {
  var qr = q
  var num = 0
  while (!qr.isEmpty) {
    val (digit, newQ) = qr.dequeue
    num *= 10
    num += digit
    qr = newQ
  }
  num
}

Este código sigue siendo eficiente, no requiere recursividad y no necesita preocuparse si tiene que hacer una copia de su cola o no antes de llamar toNum. Naturalmente, evité reutilizar variables para otros fines, y ningún código fuera de esta función las ve, así que no necesito preocuparme de que sus valores cambien de una línea a la siguiente, excepto cuando lo hago explícitamente.

Scala optó por dejar que el programador hiciera eso, si el programador consideraba que era la mejor solución. Otros idiomas han optado por dificultar dicho código. El precio que paga Scala (y cualquier lenguaje con mutabilidad generalizada) es que el compilador no tiene tanta libertad para optimizar el código como lo haría de otra manera. La respuesta de Java a eso es optimizar el código en función del perfil de tiempo de ejecución. Podríamos seguir y seguir acerca de los pros y los contras de cada lado.

Personalmente, creo que Scala logra el equilibrio correcto, por ahora. No es perfecto, de lejos. Creo que tanto Clojure como Haskell tienen nociones muy interesantes que Scala no adoptó, pero Scala también tiene sus propias fortalezas. Veremos lo que viene en el futuro.

Daniel C. Sobral
fuente
Un poco tarde, pero ... ¿Hace var qr = quna copia de q?
55
@davips No hace una copia del objeto al que hace referencia q. Realiza una copia, en la pila, no en el montón, de la referencia a ese objeto. En cuanto al rendimiento, tendrá que ser más claro sobre de qué "habla".
Daniel C. Sobral
Ok, con su ayuda y alguna información ( (x::xs).drop(1)es exactamente xs, no una "copia" de xs) aquí podría entender el enlace . tnx!
"Este código sigue siendo eficiente" - ¿lo es? Dado que qres una cola inmutable, cada vez que qr.dequeuese llama a la expresión , hace un new Queue(vea < github.com/scala/scala/blob/2.13.x/src/library/scala/collection/… ).
Owen
@Owen Sí, pero tenga en cuenta que es un objeto poco profundo. El código sigue siendo O (n) ya sea mutable, si copia la cola o inmutable.
Daniel C. Sobral
61

vales final, es decir, no se puede configurar. Piensa finalen Java.

Jackson Davis
fuente
3
Pero si entiendo correctamente (no soy un experto en Scala), las val variables son inmutables, pero los objetos a los que hacen referencia no tienen que serlo. Según el enlace que Stefan publicó: "Aquí la referencia de nombres no se puede cambiar para apuntar a una matriz diferente, pero la matriz en sí se puede modificar. En otras palabras, los contenidos / elementos de la matriz se pueden modificar". Entonces es como finalfunciona en Java.
Daniel Pryden el
3
Exactamente por qué lo publiqué tal como está. Puedo recurrir +=a un hashmap mutabled definido como valmuy bien: creo que es exactamente cómo finalfunciona en Java
Jackson Davis
Ack, pensé que los tipos de escala incorporados podrían funcionar mejor que simplemente permitir la reasignación. Necesito verificar los hechos.
Stefan Kendall, el
Estaba confundiendo los tipos de secuencia inmutables de scala con la noción general. La programación funcional me tiene todo cambiado.
Stefan Kendall, el
Agregué y eliminé un personaje ficticio en su respuesta para poder darle el voto positivo.
Stefan Kendall, el
56

En lenguaje sencillo:

var = var iable

val = v ariable + fin al

Ajay Gupta
fuente
20

La diferencia es que a varse puede reasignar a mientras que a valno se puede. La mutabilidad, o lo que sea de lo que realmente se asigna, es un problema secundario:

import collection.immutable
import collection.mutable
var m = immutable.Set("London", "Paris")
m = immutable.Set("New York") //Reassignment - I have change the "value" at m.

Mientras:

val n = immutable.Set("London", "Paris")
n = immutable.Set("New York") //Will not compile as n is a val.

Y por lo tanto:

val n = mutable.Set("London", "Paris")
n = mutable.Set("New York") //Will not compile, even though the type of n is mutable.

Si está creando una estructura de datos y todos sus campos son vals, entonces esa estructura de datos es inmutable, ya que su estado no puede cambiar.

oxbow_lakes
fuente
1
Eso solo es cierto si las clases de esos campos también son inmutables.
Daniel C. Sobral
Sí, iba a poner eso, ¡pero pensé que podría ser un paso demasiado lejos! También es un punto discutible que diría; desde una perspectiva (aunque no funcional) su estado no cambia incluso si el estado de su estado lo hace.
oxbow_lakes
¿Por qué sigue siendo tan difícil crear un objeto inmutable en un lenguaje JVM? Además, ¿por qué Scala no hizo que los objetos sean inmutables por defecto?
Derek Mahar
19

valsignifica inmutable y varsignifica mutable.

Discusión completa

Stefan Kendall
fuente
1
Esto simplemente no es cierto. El artículo vinculado proporciona una matriz mutable y lo llama inmutable. No hay fuente seria.
Klaus Schulz
No es cierto de hecho. Pruebe que val b = Array [Int] (1,2,3) b (0) = 4 println (b.mkString ("")) println ("")
Guillaume
13

Pensando en términos de C ++,

val x: T

es análogo al puntero constante a datos no constantes

T* const x;

mientras

var x: T 

es análogo al puntero no constante a datos no constantes

T* x;

Favoreciendo val sobre varaumenta inmutabilidad de la base de código que puede facilitar su corrección, concurrencia y comprensibilidad.

Para comprender el significado de tener un puntero constante a datos no constantes, considere el siguiente fragmento de Scala:

val m = scala.collection.mutable.Map(1 -> "picard")
m // res0: scala.collection.mutable.Map[Int,String] = HashMap(1 -> picard)

Aquí el "puntero" val m es constante, por lo que no podemos reasignarlo para que apunte a algo más así

m = n // error: reassignment to val

sin embargo, podemos cambiar los datos no constantes en sí mismos que mapuntan a gustar

m.put(2, "worf")
m // res1: scala.collection.mutable.Map[Int,String] = HashMap(1 -> picard, 2 -> worf)
Mario Galic
fuente
1
Creo que Scala no llevó la inmutabilidad a su conclusión final: puntero constante y datos constantes. Scala perdió la oportunidad de hacer que los objetos sean inmutables por defecto. En consecuencia, Scala no tiene la misma noción de valor que Haskell.
Derek Mahar
@DerekMahar tiene razón, pero un objeto puede presentarse como perfectamente inmutable, mientras sigue usando mutabilidad en su implementación, por ejemplo , por razones de rendimiento. ¿Cómo podría el compilador ser capaz de separar entre la mutabilidad verdadera y la mutabilidad solo interna?
francoisr
8

"val significa inmutable y var significa mutable".

Parafraseando, "val significa valor y var significa variable".

Una distinción que resulta ser extremadamente importante en informática (porque esos dos conceptos definen la esencia misma de la programación), y que OO ha logrado difuminar casi por completo, porque en OO, el único axioma es que "todo es un objeto". Y eso como consecuencia, muchos programadores en estos días tienden a no entender / apreciar / reconocer, porque se les ha lavado el cerebro para que "piensen de la manera OO" exclusivamente. A menudo, los objetos variables / mutables se usan como en todas partes , cuando los objetos de valor / inmutables podrían / ​​habrían sido mejores.

Erwin Smout
fuente
Esta es la razón por la que prefiero Haskell sobre Java, por ejemplo.
Derek Mahar
6

val significa inmutable y var significa mutable

puedes pensar valcomo Java finalKey Language Language World o C ++ Language constKey World。

Rollen Holt
fuente
1

Valsignifica su final , no se puede reasignar

Mientras que, Varpuede ser reasignado más tarde .

Crime_Master_GoGo
fuente
1
¿En qué se diferencia esta respuesta de las 12 respuestas ya enviadas?
jwvh
0

Es tan simple como su nombre.

var significa que puede variar

val significa invariable

usuario105003
fuente
0

Val: los valores son constantes de almacenamiento escritas. Una vez creado, su valor no puede reasignarse. Se puede definir un nuevo valor con la palabra clave val.

p.ej. val x: Int = 5

Aquí el tipo es opcional ya que scala puede inferirlo del valor asignado.

Var: las variables son unidades de almacenamiento tipificadas a las que se les puede asignar valores nuevamente siempre que se reserve espacio en la memoria.

p.ej. var x: Int = 5

JVM desasigna automáticamente los datos almacenados en ambas unidades de almacenamiento una vez que ya no son necesarios.

En escala, los valores son preferibles a las variables debido a la estabilidad, esto trae al código particularmente en código concurrente y multiproceso.

SunTech
fuente
0

Aunque muchos ya han respondido la diferencia entre Val y var . Pero un punto a notar es que val no es exactamente como final palabra clave .

Podemos cambiar el valor de val usando recursividad pero nunca podemos cambiar el valor de final. Final es más constante que Val.

def factorial(num: Int): Int = {
 if(num == 0) 1
 else factorial(num - 1) * num
}

Los parámetros del método son por defecto val y en cada valor de llamada se cambia.

Mahesh Chand
fuente