`def` vs` val` vs `lazy val` valutazione in Scala

Ho ragione a capirlo

Sì, anche se per il terzo vorrei dire “quando viene eseguita questa istruzione”, perché, ad esempio:

 def foo() { new { val a: Any = sys.error("b is " + b) val b: Any = sys.error("a is " + a) } } 

Questo dà "b is null" . b non viene mai valutato e il suo errore non viene mai generato. Ma è nel campo di applicazione non appena il controllo entra nel blocco.

Sì, ma c’è un bel trucco: se hai un valore pigro, e durante la prima valutazione avrà un’eccezione, la prossima volta che proverai ad accedervi cercherà di rivalutare se stesso.

Ecco un esempio:

 scala> import io.Source import io.Source scala> class Test { | lazy val foo = Source.fromFile("./bar.txt").getLines | } defined class Test scala> val baz = new Test baz: Test = [email protected] //right now there is no bar.txt scala> baz.foo java.io.FileNotFoundException: ./bar.txt (No such file or directory) at java.io.FileInputStream.open(Native Method) at java.io.FileInputStream.(FileInputStream.java:137) ... // now I've created empty file named bar.txt // class instance is the same scala> baz.foo res2: Iterator[String] = empty iterator 

Vorrei spiegare le differenze attraverso l’esempio che ho eseguito in REPL.Mi credo che questo semplice esempio sia più facile da comprendere e spiega le differenze concettuali.

Qui, sto creando un val result1, un lazy val result2 e un def result3 ognuno dei quali ha un tipo String.

UN). val

 scala> val result1 = {println("hello val"); "returns val"} hello val result1: String = returns val 

Qui, println viene eseguito perché il valore di result1 è stato calcolato qui. Quindi, ora risultato1 farà sempre riferimento al suo valore, ad esempio “ritorna val”.

 scala> result1 res0: String = returns val 

Quindi, ora, puoi vedere che risultato1 ora si riferisce al suo valore. Si noti che, l’istruzione println non viene eseguita qui perché il valore per result1 è già stato calcolato quando è stato eseguito per la prima volta. Quindi, in seguito, result1 restituirà sempre lo stesso valore e l’istruzione println non verrà mai eseguita nuovamente perché il calcolo per ottenere il valore di risultato1 è già stato eseguito.

B). pigro val

 scala> lazy val result2 = {println("hello lazy val"); "returns lazy val"} result2: String =  

Come possiamo vedere qui, l’istruzione println non viene eseguita qui e il valore non è stato calcolato. Questa è la natura della pigrizia.

Ora, quando mi riferisco al risultato 2 per la prima volta, verrà eseguita l’istruzione println e il valore verrà calcolato e assegnato.

 scala> result2 hello lazy val res1: String = returns lazy val 

Ora, quando mi riferisco nuovamente a result2, questa volta vedremo solo il valore che detiene e l’istruzione println non verrà eseguita. D’ora in poi, risultato2 si comporterà semplicemente come una val e restituirà il suo valore memorizzato nella cache tutto il tempo.

 scala> result2 res2: String = returns lazy val 

C). DEF

In caso di def, il risultato dovrà essere calcolato ogni volta che viene chiamato result3. Questo è anche il motivo principale per cui definiamo i metodi come def in scala perché i metodi devono calcolare e restituire un valore ogni volta che viene chiamato all’interno del programma.

 scala> def result3 = {println("hello def"); "returns def"} result3: String scala> result3 hello def res3: String = returns def scala> result3 hello def res4: String = returns def 

Una buona ragione per scegliere la def sopra val , specialmente nelle classi astratte (o nei tratti che sono usati per imitare le interfacce di Java), è che è ansible sovrascrivere una def con una val in sottoclassi, ma non viceversa.

Per quanto riguarda i lazy , ci sono due cose che posso vedere che si dovrebbero avere in mente. Il primo è che lazy introduce un sovraccarico di runtime, ma suppongo che avresti bisogno di confrontare la tua situazione specifica per scoprire se questo ha effettivamente un impatto significativo sulle prestazioni del runtime. L’altro problema con lazy è che probabilmente ritarda l’aumento di un’eccezione, il che potrebbe rendere più difficile ragionare sul tuo programma, perché l’eccezione non viene lanciata in anticipo ma solo al primo utilizzo.

Hai ragione. Per prove dalla specifica :

Da “3.3.1 Tipi di metodi” (per def ):

I metodi senza parametri definiscono espressioni che vengono rivalutate ogni volta che si fa riferimento al nome del metodo senza parametri.

Da “4.1 Dichiarazione di valore e definizioni”:

Una definizione di valore val x : T = e definisce x come nome del valore risultante dalla valutazione di e .

Una definizione di valore lazy valuta il suo lato destro e la prima volta che si accede al valore.

def definisce un metodo. Quando chiami il metodo, viene eseguito il metodo ofcourse.

val definisce un valore (una variabile immutabile). L’espressione di assegnazione viene valutata quando il valore è inizializzato.

lazy val definisce un valore con l’inizializzazione ritardata. Sarà inizializzato quando viene utilizzato per la prima volta, quindi l’espressione di assegnazione verrà valutata in quel momento.

Un nome qualificato da def viene valutato sostituendo il nome e la relativa espressione RHS ogni volta che il nome appare nel programma. Pertanto, questa sostituzione verrà eseguita ogni volta che il nome appare nel programma.

Un nome qualificato da val viene valutato immediatamente quando il controllo raggiunge la sua espressione RHS. Pertanto, ogni volta che il nome appare nell’espressione, sarà visto come il valore di questa valutazione.

Un nome qualificato da lazy val segue la stessa politica di quella di val qualifica con un’eccezione che il suo RHS verrà valutato solo quando il controllo raggiunge il punto in cui il nome viene utilizzato per la prima volta

Dovrebbe indicare una potenziale trappola per quanto riguarda l’utilizzo di val quando si lavora con valori non noti fino al runtime.

Prendi, ad esempio, request: HttpServletRequest

Se dovessi dire:

 val foo = request accepts "foo" 

Si otterrebbe un’eccezione di puntatore nullo al punto di inizializzazione della val , la richiesta non ha pippo (sarebbe solo nota al runtime).

Quindi, a seconda delle spese di accesso / calcolo, def o lazy val sono quindi scelte appropriate per i valori determinati dal runtime; quello, o un val che è esso stesso una funzione anonima che recupera i dati di runtime (anche se quest’ultimo sembra un caso un po ‘più marginale)