2015-07-31 11 views
9

Ho notato che lazy val ripete di calcolo più volte (in caso di eccezione):È corretto il comportamento che `lazy val` si comporta come` def` in caso di eccezione?

scala> lazy val aaa = {println("calc"); sys.error("aaaa")} 
aaa: Nothing = <lazy> 

scala> aaa 
calc 
java.lang.RuntimeException: aaaa 
    at scala.sys.package$.error(package.scala:27) 
    at .aaa$lzycompute(<console>:7) 
    at .aaa(<console>:7) 
    ... 33 elided 

scala> aaa 
calc 
java.lang.RuntimeException: aaaa 
    at scala.sys.package$.error(package.scala:27) 
    at .aaa$lzycompute(<console>:7) 
    at .aaa(<console>:7) 
    ... 33 elided 

Non dovrebbe essere come:

scala> aaa 
calc 
java.lang.RuntimeException: Not Initialized! 
caused by 
java.lang.RuntimeException: aaaa 

scala> aaa 
java.lang.RuntimeException: Not Initialized! 
caused by 
java.lang.RuntimeException: aaaa 
+2

Ecco come ho letto dovrebbe funzionare, qualche tempo fa. –

risposta

5

In this postale spiegano molto bene come è lazy val compilato dal compilatore Scala. Fondamentalmente, se la valutazione dell'espressione fallisce, allora il segnale indicatore che il lazy val contiene i suoi dati non verrà impostato.

update1:

ritengo una ragione andando con il primo approccio potrebbe essere che la seconda può essere emulato usando due lazy val s, senza gravare l'implementazione sottostante con più variabili volatili:

scala> lazy val _aaa = Try {println("calc"); sys.error("aaaa")} 
_aaa: scala.util.Try[Nothing] = <lazy> 

scala> lazy val aaa = _aaa.get 
aaa: Nothing = <lazy> 

scala> aaa 
calc 
java.lang.RuntimeException: aaaa 
    at scala.sys.package$.error(package.scala:27) 
    at $anonfun$_aaa$1.apply(<console>:10) 
    at $anonfun$_aaa$1.apply(<console>:10) 
    at scala.util.Try$.apply(Try.scala:191) 
    at ._aaa$lzycompute(<console>:10) 
    at ._aaa(<console>:10) 
    at .aaa$lzycompute(<console>:11) 
    at .aaa(<console>:11) 
    ... 33 elided 

scala> aaa 
java.lang.RuntimeException: aaaa 
    at scala.sys.package$.error(package.scala:27) 
    at $anonfun$_aaa$1.apply(<console>:10) 
    at $anonfun$_aaa$1.apply(<console>:10) 
    at scala.util.Try$.apply(Try.scala:191) 
    at ._aaa$lzycompute(<console>:10) 
    at ._aaa(<console>:10) 
    at .aaa$lzycompute(<console>:11) 
    at .aaa(<console>:11) 
    ... 33 elided 

Update2:

Come @Silly Freak ha proposto nel suo commento,

scala> lazy val _aaa = Try {println("calc"); sys.error("aaaa")} 
_aaa: scala.util.Try[Nothing] = <lazy> 

scala> def aaa = _aaa.get 
aaa: Nothing 

potrebbero funzionare anche meglio, in quanto possiamo evitare di avere due lazy val s.

+0

Sai già come funziona. La mia domanda riguarda la correttezza di approccio o forse un approccio migliore? Come credo, nulla impedisce loro (come vedo io) dall'implementazione della corretta gestione delle eccezioni all'interno di lazy-compute. – dk14

+0

Voglio dire, potrebbero avere un altro bit per isFailure (almeno) o ricordare l'eccezione (al massimo, ma più costosa). Per ora, '0' significa sia fallito/non inizializzato, quindi non puoi distinguerli. – dk14

+1

Non posso dirlo, ma probabilmente per motivi di prestazioni (https://twitter.com/djspiewak/status/302489756552536064). Per me entrambi gli approcci sembrano ragionevoli. Sebbene non sia esattamente lo stesso, ma a seconda del tuo caso d'uso, puoi emulare il tuo approccio facendo 'lazy val aaa = Try {println (" calc "); sys.error ("aaaa")} ' – kosii