2011-09-20 14 views
193

Ho notato che Scala fornisce lazy vals. Ma non capisco quello che fanno.Che cosa fa un val pigro?

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 

Il REPL dimostra che y è un lazy val, ma come è diverso da un normale val?

risposta

267

La differenza tra loro è che un val viene eseguito quando viene definito mentre un lazy val viene eseguito quando viene effettuato l'accesso la prima volta.

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 

Contrariamente a un metodo (definito con def) un lazy val viene eseguita una volta e poi mai più. Ciò può essere utile quando un'operazione richiede molto tempo per essere completata e quando non è sicuro se verrà utilizzata in un secondo momento.

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 = [email protected] // we have to wait two seconds to the result 

scala> new Y 
res6: Y = [email protected] // this appears immediately 

Qui, quando i valori x e y non sono mai utilizzati, solo x risorse inutilmente spreco. Se supponiamo che non abbia effetti collaterali e che non sappiamo con quale frequenza si acceda (mai, una volta, migliaia di volte) è inutile dichiararlo come def poiché non vogliamo eseguirlo più volte.

Se si desidera sapere come sono implementate le misure lazy vals, vedere questo question.

+55

Come supplemento: @ViktorKlang postato su Twitter: ["Scala Scala poco nota: se l'inizializzazione di un valore lazy lancia un exce ption, tenterà di reinizializzare la val al prossimo accesso. "] (https://twitter.com/#!/viktorklang/status/104483846002704384) –

51

Questa funzione non solo aiuta a ritardare i calcoli costosi, ma è anche utile per costruire strutture reciprocamente dipendenti o cicliche. Per esempio. questo porta ad un overflow dello 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 

Ma con vals pigre funziona bene

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() 
+0

Ma porterà alla stessa StackOverflowException se il tuo metodo toString emette" foo " attributo. Bel esempio di "pigro" comunque !!! –

19

anche lazy è utile senza dipendenze cicliche, come nel codice seguente:

abstract class X { 
    val x: String 
    println ("x is "+x.length) 
} 

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

Accesso Y genererà ora un'eccezione di puntatore nullo, perché x non è ancora stato inizializzato. Di seguito, invece, funziona bene:

abstract class X { 
    val x: String 
    println ("x is "+x.length) 
} 

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

EDIT: il seguente funziona anche:

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

Questo viene chiamato "inizializzatore precoce". Vedi this SO question per maggiori dettagli.

+11

Puoi chiarire perché la dichiarazione di Y non inizializza immediatamente la variabile "x" nel primo esempio prima di chiamare il costruttore genitore? – Ashoat

+2

Perché il costruttore della superclasse è il primo che viene chiamato implicitamente. –

+0

@Ashoat Vedere [questo collegamento] (https://github.com/scala/scala.github.com/blob/master/tutorials/FAQ/initialization-order.md) per una spiegazione del motivo per cui non è stato inizializzato. – Jus12

21

Un lazy val è più facilmente inteso come "memoized def".

Come un def, un valore lazy val non viene valutato finché non viene richiamato. Ma il risultato viene salvato in modo che le successive chiamate restituiscano il valore salvato. Il risultato memorizzato occupa spazio nella struttura dei dati, come una val.

Come altri hanno menzionato, i casi d'uso per un valore lazy valgono il rinvio di calcoli costosi finché non sono necessari e memorizzano i loro risultati, e per risolvere certe dipendenze circolari tra valori.

Lazy vals sono in effetti implementate più o meno come defn memoized. Si può leggere sui dettagli della loro attuazione qui:

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

23

Capisco che la risposta viene data, ma ho scritto un semplice esempio per rendere più facile da capire per i principianti come me:

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

uscita di codice è:

x 
----- 
y 
y is: 18 

Come si può notare, x è stampato quando è inizializzato, ma y non viene stampata quando è ini tializzato nello stesso modo (ho preso x come var intenzionalmente qui - per spiegare quando si inizializza). Successivamente, quando viene chiamato y, viene inizializzato e viene preso in considerazione il valore dell'ultima "x", ma non quello precedente.

Spero che questo aiuti.

0
scala> lazy val lazyEight = { 
    | println("I am lazy !") 
    | 8 
    | } 
lazyEight: Int = <lazy> 

scala> lazyEight 
I am lazy ! 
res1: Int = 8 
  • Tutti vals vengono inizializzati durante la costruzione dell'oggetto
  • Uso parola pigro per rinviare inizializzazione fino prima fornitura
  • Attenzione: vals pigre non sono finale e pertanto potrebbe mostrare prestazioni Inconvenienti