Scala Language
Var, Val y Def
Buscar..
Observaciones
Como los val
son semánticamente estáticos, se inicializan "en el lugar" donde aparecen en el código. Esto puede producir un comportamiento sorprendente e indeseable cuando se usa en clases abstractas y rasgos.
Por ejemplo, digamos que nos gustaría hacer un rasgo llamado PlusOne
que defina una operación de incremento en un Int
envuelto. Dado que los Int
s son inmutables, el valor más uno se conoce en la inicialización y nunca se cambiará después, por lo que semánticamente es un valor val
. Sin embargo, definirlo de esta manera producirá un resultado inesperado.
trait PlusOne {
val i:Int
val incr = i + 1
}
class IntWrapper(val i: Int) extends PlusOne
No importa cuál es el valor i
se construye IntWrapper
con, llamando .incr
en el objeto devuelto siempre devuelve 1. Esto es porque el val incr
es inicializado en el rasgo, antes de la clase que se extiende, y en ese momento i
sólo tiene el valor por defecto de 0
. (En otras condiciones, puede completarse con Nil
, null
o un valor predeterminado similar).
La regla general, entonces, es evitar usar val
en cualquier valor que dependa de un campo abstracto. En su lugar, use lazy val
, que no evalúa hasta que se necesita, o def
, que evalúa cada vez que se llama. Sin embargo, tenga en cuenta que si el valor de lazy val
es forzado a evaluar por un val
antes de que se complete la inicialización, ocurrirá el mismo error.
Un violín (escrito en Scala-Js, pero se aplica el mismo comportamiento) se puede encontrar aquí.
Var, Val y Def
var
Una var
es una variable de referencia, similar a las variables en lenguajes como Java. Se pueden asignar diferentes objetos a una var
libremente, siempre que el objeto dado tenga el mismo tipo con el que se declaró la var
:
scala> var x = 1
x: Int = 1
scala> x = 2
x: Int = 2
scala> x = "foo bar"
<console>:12: error: type mismatch;
found : String("foo bar")
required: Int
x = "foo bar"
^
Observe en el ejemplo anterior el tipo de la var
fue inferida por el compilador dada la primera asignación de valor.
val
Un val
es una referencia constante. Por lo tanto, no se puede asignar un nuevo objeto a un val
que ya se ha asignado.
scala> val y = 1
y: Int = 1
scala> y = 2
<console>:12: error: reassignment to val
y = 2
^
Sin embargo, el objeto al que apunta un val
no es constante. Ese objeto puede ser modificado:
scala> val arr = new Array[Int](2)
arr: Array[Int] = Array(0, 0)
scala> arr(0) = 1
scala> arr
res1: Array[Int] = Array(1, 0)
def
Una def
define un método. Un método no puede ser reasignado a
scala> def z = 1
z: Int
scala> z = 2
<console>:12: error: value z_= is not a member of object $iw
z = 2
^
En los ejemplos anteriores, val y
y def z
devuelven el mismo valor. Sin embargo, una def
se evalúa cuando se llama , mientras que una val
o var
se evalúa cuando se asigna . Esto puede resultar en un comportamiento diferente cuando la definición tiene efectos secundarios:
scala> val a = {println("Hi"); 1}
Hi
a: Int = 1
scala> def b = {println("Hi"); 1}
b: Int
scala> a + 1
res2: Int = 2
scala> b + 1
Hi
res3: Int = 2
Funciones
Como las funciones son valores, se pueden asignar a val
/ var
/ def
s. Todo lo demás funciona de la misma manera que arriba:
scala> val x = (x: Int) => s"value=$x"
x: Int => String = <function1>
scala> var y = (x: Int) => s"value=$x"
y: Int => String = <function1>
scala> def z = (x: Int) => s"value=$x"
z: Int => String
scala> x(1)
res0: String = value=1
scala> y(2)
res1: String = value=2
scala> z(3)
res2: String = value=3
Perezoso val
lazy val
es una función de lenguaje en la que la inicialización de un val
se retrasa hasta que se accede por primera vez. Después de ese punto, actúa como un val
normal.
Para usarlo agregue la palabra clave lazy
antes de val
. Por ejemplo, usando el REPL:
scala> lazy val foo = {
| println("Initializing")
| "my foo value"
| }
foo: String = <lazy>
scala> val bar = {
| println("Initializing bar")
| "my bar value"
| }
Initializing bar
bar: String = my bar value
scala> foo
Initializing
res3: String = my foo value
scala> bar
res4: String = my bar value
scala> foo
res5: String = my foo value
Este ejemplo demuestra el orden de ejecución. Cuando el lazy val
se declara, todo lo que se guarda en el foo
valor es una llamada a la función vago que no ha sido evaluado. Cuando el normal val
se establece, vemos el println
llamada de ejecutar y se le asigna el valor a bar
. Cuando evaluamos foo
la primera vez, vemos println
ejecuta println
, pero no cuando se evalúa la segunda vez. De manera similar, cuando se evalúa la bar
, no vemos que se ejecute println
, solo cuando se declara.
Cuando usar 'perezoso'
La inicialización es computacionalmente costosa y el uso de
val
es raro.lazy val tiresomeValue = {(1 to 1000000).filter(x => x % 113 == 0).sum} if (scala.util.Random.nextInt > 1000) { println(tiresomeValue) }
tiresomeValue
tarda mucho tiempo en calcularse, y no siempre se usa. Si se convierte en un valorlazy val
ahorra cómputo innecesario.Resolución de dependencias cíclicas.
Veamos un ejemplo con dos objetos que deben declararse al mismo tiempo durante la creación de instancias:
object comicBook { def main(args:Array[String]): Unit = { gotham.hero.talk() gotham.villain.talk() } } class Superhero(val name: String) { lazy val toLockUp = gotham.villain def talk(): Unit = { println(s"I won't let you win ${toLockUp.name}!") } } class Supervillain(val name: String) { lazy val toKill = gotham.hero def talk(): Unit = { println(s"Let me loosen up Gotham a little bit ${toKill.name}!") } } object gotham { val hero: Superhero = new Superhero("Batman") val villain: Supervillain = new Supervillain("Joker") }
Sin la palabra clave
lazy
, los objetos respectivos no pueden ser miembros de un objeto. La ejecución de dicho programa daría lugar a unajava.lang.NullPointerException
. Mediante el usolazy
, la referencia se puede asignar antes de que se inicialice, sin temor a tener un valor sin inicializar.
Sobrecarga def
Puede sobrecargar una def
si la firma es diferente:
def printValue(x: Int) {
println(s"My value is an integer equal to $x")
}
def printValue(x: String) {
println(s"My value is a string equal to '$x'")
}
printValue(1) // prints "My value is an integer equal to 1"
printValue("1") // prints "My value is a string equal to '1'"
Esto funciona de la misma manera ya sea dentro de clases, rasgos, objetos o no.
Parámetros con nombre
Al invocar una def
, los parámetros pueden asignarse explícitamente por nombre. Si lo hace, significa que no necesitan ser ordenados correctamente. Por ejemplo, defina printUs()
como:
// print out the three arguments in order.
def printUs(one: String, two: String, three: String) =
println(s"$one, $two, $three")
Ahora se puede llamar de estas maneras (entre otras):
printUs("one", "two", "three")
printUs(one="one", two="two", three="three")
printUs("one", two="two", three="three")
printUs(three="three", one="one", two="two")
Esto hace que se impriman one, two, three
en todos los casos.
Si no se nombran todos los argumentos, los primeros argumentos se comparan por orden. Ningún argumento posicional (sin nombre) puede seguir a uno nombrado:
printUs("one", two="two", three="three") // prints 'one, two, three'
printUs(two="two", three="three", "one") // fails to compile: 'positional after named argument'