Cosa fa un pigro val?


248

Ho notato che Scala fornisce lazy vals. Ma non capisco quello che fanno.

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 mostra che yè un lazy val, ma come è diverso da un normale val?

Risposte:


335

La differenza tra loro è che a valviene eseguito quando è definito mentre a lazy valviene eseguito quando si accede per 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) a lazy valviene eseguito una volta e poi mai più. Ciò può essere utile quando un'operazione richiede molto tempo per il completamento e quando non è sicuro se verrà utilizzata in seguito.

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

Qui, quando i valori xe ynon vengono mai utilizzati, solo xsprecare inutilmente risorse. Se supponiamo che ynon abbia effetti collaterali e che non sappiamo con quale frequenza si acceda (mai, una volta, migliaia di volte), è inutile dichiararlo come defpoiché non vogliamo eseguirlo più volte.

Se vuoi sapere come lazy valsvengono implementate, vedi questa domanda .



@PeterSchmitz E lo trovo terribile. Confronta con Lazy<T>in .NET
Pavel Voronin,

61

Questa funzione aiuta non solo a ritardare calcoli costosi, ma è anche utile per costruire strutture cicliche o dipendenti reciprocamente. Ad esempio, questo porta a 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 Val Lazy 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()

Ma porterà allo stesso StackOverflowException se il tuo metodo toString restituisce l'attributo "pippo". Bel esempio di "pigro" comunque !!!
Fuad Efendi,

39

Capisco che la risposta è stata data, ma ho scritto un semplice esempio per renderlo 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)

L'output del codice sopra è:

x
-----
y
y is: 18

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

Spero che questo ti aiuti.


35

Un Val pigro è più facilmente inteso come " def memorizzato (no-arg) def".

Come un def, un valore pigro non viene valutato fino a quando non viene invocato. Ma il risultato viene salvato in modo che le invocazioni successive restituiscano il valore salvato. Il risultato memorizzato occupa spazio nella struttura dei dati, come una val.

Come altri hanno già detto, i casi d'uso di un val pigro consistono nel rinviare calcoli costosi fino a quando non sono necessari e archiviare i loro risultati, e per risolvere alcune dipendenze circolari tra i valori.

Le val pigre sono infatti implementate più o meno come def memorizzate. Puoi leggere i dettagli della loro implementazione qui:

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


1
forse piuttosto come "def memorizzato che accetta 0 argomenti".
Andrey Tyukin,

19

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

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

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

L'accesso Ygenererà ora un'eccezione puntatore null, perché xnon è ancora inizializzato. Quanto segue, tuttavia, funziona bene:

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

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

EDIT: funzionerà anche quanto segue:

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

Questo si chiama "inizializzatore anticipato". Vedi questa domanda SO 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 principale?
Ashoat,

2
Perché il costruttore di superclasse è il primo a essere implicitamente chiamato.
Stevo Slavić,

@Ashoat Vedere questo link per una spiegazione del perché non è stato inizializzato.
Jus12

4

Una dimostrazione di lazy- come definito sopra - esecuzione quando definita vs esecuzione quando si accede: (usando 2.12.7 scala shell)

// compiler says this is ok when it is lazy
scala> lazy val t: Int = t 
t: Int = <lazy>
//however when executed, t recursively calls itself, and causes a StackOverflowError
scala> t             
java.lang.StackOverflowError
...

// when the t is initialized to itself un-lazily, the compiler warns you of the recursive call
scala> val t: Int = t
<console>:12: warning: value t does nothing other than call itself recursively
   val t: Int = t

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

scala> lazyEight
I am lazy !
res1: Int = 8
  • Tutte le val vengono inizializzate durante la costruzione dell'oggetto
  • Utilizzare la parola chiave lazy per differire l'inizializzazione fino al primo utilizzo
  • Attenzione : i valori pigri non sono definitivi e pertanto potrebbero presentare degli svantaggi nelle prestazioni
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.