koans - scala learn
¿Qué hace un val perezoso? (6)
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 no se imprime cuando se inicializa de la misma manera (he tomado x como var intencionalmente aquí - para explicar cuándo se inicializa y). A continuación, cuando se llama y, se inicializa y se toma en cuenta el valor de la última ''x'', pero no el anterior.
Espero que esto ayude.
Me di cuenta de 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 = <lazy>
scala> x
res0: Int = 15
scala> y
res1: Int = 13
El REPL muestra que y
es un valor lazy val
, pero ¿en qué se diferencia de un valor normal?
Esta característica ayuda no solo a retrasar los cálculos costosos, sino que también es útil para construir estructuras mutuas o cíclicas. Por ejemplo, esto lleva a un desbordamiento de pila:
trait Foo { val foo: Foo }
case class Fee extends Foo { val foo = Faa() }
case class Faa extends Foo { val foo = Fee() }
println(Fee().foo)
//Exception
Pero con vals perezosos 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()
La diferencia entre ellos es que un val
se ejecuta cuando se define, mientras que un 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 = <lazy>
scala> x
res2: Int = 15
scala> y
y
res3: Int = 13
scala> y
res4: Int = 13
En contraste con 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 está seguro de si se usa más adelante.
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
innecesariamente desperdicia recursos. Si suponemos que y
no tiene efectos secundarios y 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 question .
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
ahora lanzará una excepción de puntero nulo, porque x
aún no está 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.
Una valla perezosa se entiende más fácilmente como una " memoized (no-arg)".
Al igual que una definición, un valor 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 memorizado ocupa espacio en su estructura de datos, como un val.
Como han mencionado otros, los casos de uso para un valor perezoso son diferir los costosos cálculos hasta que sean necesarios y almacenar sus resultados, y resolver ciertas dependencias circulares entre los valores.
De hecho, los valores perezosos se implementan más o menos como definiciones memorizadas. Puedes leer sobre los detalles de su implementación aquí:
http://docs.scala-lang.org/sips/pending/improved-lazy-val-initialization.html
scala> lazy val lazyEight = {
| println("I am lazy !")
| 8
| }
lazyEight: Int = <lazy>
scala> lazyEight
I am lazy !
res1: Int = 8
- Todos los valores se inicializan durante la construcción del objeto.
- Use la palabra clave perezosa para diferir la inicialización hasta el primer uso
- Atención : los valores perezosos no son definitivos y, por lo tanto, pueden presentar inconvenientes de rendimiento