¿Qué hace un vago val?

Noté que Scala proporciona lazy vals . Pero no entiendo lo que hacen.

 scala> val x = 15 x: Int = 15 scala> lazy val y = 13 y: Int =  scala> x res0: Int = 15 scala> y res1: Int = 13 

El REPL muestra que y es un valor lazy val , pero ¿cómo es diferente de un valor normal?

La diferencia entre ellos es que un val se ejecuta cuando se define, mientras que un valor lazy val se ejecuta cuando se accede por primera vez.

 scala> val x = { println("x"); 15 } x x: Int = 15 scala> lazy val y = { println("y"); 13 } y: Int =  scala> x res2: Int = 15 scala> y y res3: Int = 13 scala> y res4: Int = 13 

A diferencia de un método (definido con def ), un lazy val se ejecuta una vez y luego nunca más. Esto puede ser útil cuando una operación tarda mucho tiempo en completarse y cuando no se sabe con seguridad si se usará más tarde.

 scala> class X { val x = { Thread.sleep(2000); 15 } } defined class X scala> class Y { lazy val y = { Thread.sleep(2000); 13 } } defined class Y scala> new X res5: X = X@262505b7 // we have to wait two seconds to the result scala> new Y res6: Y = Y@1555bd22 // this appears immediately 

Aquí, cuando los valores x e y nunca se usan, solo x desperdicia recursos innecesariamente. Si suponemos que y no tiene efectos secundarios y que no sabemos con qué frecuencia se accede (nunca, una vez, miles de veces) es inútil declararlo como def ya que no queremos ejecutarlo varias veces.

Si desea saber cómo se implementan los lazy vals , consulte esta pregunta .

Esta característica ayuda no solo a retrasar costosos cálculos, sino que también es útil para construir estructuras dependientes o cíclicas mutuas. Por ejemplo, esto lleva a un desbordamiento de la stack:

 trait Foo { val foo: Foo } case class Fee extends Foo { val foo = Faa() } case class Faa extends Foo { val foo = Fee() } println(Fee().foo) //StackOverflowException 

Pero con vals perezoso funciona bien

 trait Foo { val foo: Foo } case class Fee extends Foo { lazy val foo = Faa() } case class Faa extends Foo { lazy val foo = Fee() } println(Fee().foo) //Faa() 

Entiendo que la respuesta está dada pero escribí un ejemplo simple para que sea fácil de entender para principiantes como yo:

 var x = { println("x"); 15 } lazy val y = { println("y"); x+1 } println("-----") x = 17 println("y is: " + y) 

La salida del código anterior es:

 x ----- y y is: 18 

Como se puede ver, x se imprime cuando se inicializa, pero y no se imprime cuando se inicializa de la misma manera (He tomado x como var intencionalmente aquí – para explicar cuándo y se inicializa). Luego cuando se invoca a y, se inicializa y se toma en cuenta el valor de la última ‘x’, pero no el anterior.

Espero que esto ayude.

Un vago val es más fácil de entender como una “definición memorable “.

Al igual que una def, un val perezoso no se evalúa hasta que se invoca. Pero el resultado se guarda para que las invocaciones posteriores devuelvan el valor guardado. El resultado memoial ocupa espacio en su estructura de datos, como un valor.

Como han mencionado otros, los casos de uso de un valor diferido son diferir costosos cálculos hasta que se necesiten y almacenar sus resultados, y resolver ciertas dependencias circulares entre valores.

Lazy vals de hecho se implementan más o menos como defs memorados. Puede leer sobre los detalles de su implementación aquí:

http://docs.scala-lang.org/sips/pending/improved-lazy-val-initialization.html

También lazy es útil sin dependencias cíclicas, como en el siguiente código:

 abstract class X { val x: String println ("x is "+x.length) } object Y extends X { val x = "Hello" } Y 

Acceder a Y arrojará ahora una excepción de puntero nulo, porque x aún no se ha inicializado. Lo siguiente, sin embargo, funciona bien:

 abstract class X { val x: String println ("x is "+x.length) } object Y extends X { lazy val x = "Hello" } Y 

EDITAR: lo siguiente también funcionará:

 object Y extends { val x = "Hello" } with X 

Esto se llama un “inicializador temprano”. Vea esta pregunta SO para más detalles.

 scala> lazy val lazyEight = { | println("I am lazy !") | 8 | } lazyEight: Int =  scala> lazyEight I am lazy ! res1: Int = 8 
  • Todos los vals se inicializan durante la construcción del objeto
  • Utilice la palabra clave diferida para diferir la inicialización hasta el primer uso
  • Atención : Lazy vals no son definitivas y, por lo tanto, pueden mostrar inconvenientes de rendimiento