Scala Language
Var, Val e Def
Ricerca…
Osservazioni
Poiché val sono semanticamente statici, vengono inizializzati "sul posto" ovunque compaiano nel codice. Questo può produrre un comportamento sorprendente e indesiderato se usato in classi e tratti astratti.
Per esempio, diciamo che vorremmo creare un tratto chiamato PlusOne che definisca un'operazione di incremento su un Int avvolto. Poiché Int s è immutabile, il valore più uno è noto all'inizializzazione e non verrà mai modificato in seguito, quindi semanticamente è un valore val . Tuttavia, definirlo in questo modo produrrà un risultato inaspettato.
trait PlusOne {
val i:Int
val incr = i + 1
}
class IntWrapper(val i: Int) extends PlusOne
Non importa quale sia il valore i si costruisce IntWrapper con, chiamando .incr sul oggetto restituito sarà sempre tornare 1. Questo perché la val incr viene inizializzato nel tratto, prima della classe che estende, e in quel momento i ha solo il valore di default 0 . (In altre condizioni, potrebbe essere popolato con Nil , null o un valore predefinito simile).
La regola generale, quindi, è di evitare l'uso di val su qualsiasi valore che dipende da un campo astratto. Invece, usa lazy val , che non valuta fino a quando non è necessario, o def , che valuta ogni volta che viene chiamato. Si noti tuttavia che se il valore lazy val è forzato a valutare da una val prima che l'inizializzazione sia completata, si verificherà lo stesso errore.
Un violino (scritto in Scala-Js, ma vale lo stesso comportamento) può essere trovato qui.
Var, Val e Def
var
Una var è una variabile di riferimento, simile alle variabili in linguaggi come Java. Oggetti diversi possono essere assegnati liberamente a una var , purché l'oggetto dato abbia lo stesso tipo con cui la var stata dichiarata:
scala> var x = 1
x: Int = 1
scala> x = 2
x: Int = 2
scala> x = "foo bar"
<console>:12: error: type mismatch;
found : String("foo bar")
required: Int
x = "foo bar"
^
Nota nell'esempio sopra il tipo di var stato dedotto dal compilatore dato il primo assegnamento di valore.
val
Una val è un riferimento costante. Pertanto, un nuovo oggetto non può essere assegnato a una val che è già stata assegnata.
scala> val y = 1
y: Int = 1
scala> y = 2
<console>:12: error: reassignment to val
y = 2
^
Tuttavia, l'oggetto a cui punta val non è costante. Quell'oggetto può essere modificato:
scala> val arr = new Array[Int](2)
arr: Array[Int] = Array(0, 0)
scala> arr(0) = 1
scala> arr
res1: Array[Int] = Array(1, 0)
DEF
Una def definisce un metodo. Un metodo non può essere riassegnato a.
scala> def z = 1
z: Int
scala> z = 2
<console>:12: error: value z_= is not a member of object $iw
z = 2
^
Negli esempi precedenti, val y e def z restituiscono lo stesso valore. Tuttavia, una def viene valutata quando viene chiamata , mentre una val o var viene valutata quando viene assegnata . Ciò può comportare un comportamento diverso quando la definizione ha effetti collaterali:
scala> val a = {println("Hi"); 1}
Hi
a: Int = 1
scala> def b = {println("Hi"); 1}
b: Int
scala> a + 1
res2: Int = 2
scala> b + 1
Hi
res3: Int = 2
funzioni
Poiché le funzioni sono valori, possono essere assegnate a val / var / def s. Tutto il resto funziona allo stesso modo di sopra:
scala> val x = (x: Int) => s"value=$x"
x: Int => String = <function1>
scala> var y = (x: Int) => s"value=$x"
y: Int => String = <function1>
scala> def z = (x: Int) => s"value=$x"
z: Int => String
scala> x(1)
res0: String = value=1
scala> y(2)
res1: String = value=2
scala> z(3)
res2: String = value=3
Lazy val
lazy val è una funzione linguistica in cui l'inizializzazione di una val è ritardata fino a quando non viene acceduta per la prima volta. Dopo quel punto, si comporta come una normale val .
Per usarlo aggiungi la parola chiave lazy prima di val . Ad esempio, utilizzando il REPL:
scala> lazy val foo = {
| println("Initializing")
| "my foo value"
| }
foo: String = <lazy>
scala> val bar = {
| println("Initializing bar")
| "my bar value"
| }
Initializing bar
bar: String = my bar value
scala> foo
Initializing
res3: String = my foo value
scala> bar
res4: String = my bar value
scala> foo
res5: String = my foo value
Questo esempio dimostra l'ordine di esecuzione. Quando viene dichiarato il valore lazy val , tutto ciò che viene salvato nel valore foo è una chiamata a funzione lenta che non è stata ancora valutata. Quando il regolare val è impostato, vediamo il println chiamata esecuzione e il valore viene assegnato a bar . Quando valutiamo foo la prima volta vediamo println execute - ma non quando viene valutato la seconda volta. Allo stesso modo, quando viene valutata la bar , non vediamo println execute - solo quando è dichiarata.
Quando usare 'pigro'
L'inizializzazione è computazionalmente costosa e l'utilizzo di
valè raro.lazy val tiresomeValue = {(1 to 1000000).filter(x => x % 113 == 0).sum} if (scala.util.Random.nextInt > 1000) { println(tiresomeValue) }tiresomeValuerichiede molto tempo per il calcolo e non è sempre utilizzato. Rendendolo un valorelazy valrisparmia calcoli superflui.Risoluzione delle dipendenze cicliche
Diamo un'occhiata a un esempio con due oggetti che devono essere dichiarati contemporaneamente durante l'istanziazione:
object comicBook { def main(args:Array[String]): Unit = { gotham.hero.talk() gotham.villain.talk() } } class Superhero(val name: String) { lazy val toLockUp = gotham.villain def talk(): Unit = { println(s"I won't let you win ${toLockUp.name}!") } } class Supervillain(val name: String) { lazy val toKill = gotham.hero def talk(): Unit = { println(s"Let me loosen up Gotham a little bit ${toKill.name}!") } } object gotham { val hero: Superhero = new Superhero("Batman") val villain: Supervillain = new Supervillain("Joker") }Senza la parola chiave
lazy, i rispettivi oggetti non possono essere membri di un oggetto. L'esecuzione di tale programma comporterebbe unajava.lang.NullPointerException. Usandolazy, il riferimento può essere assegnato prima che venga inizializzato, senza timore di avere un valore non inizializzato.
Sovraccarico Def
Puoi sovraccaricare un def se la firma è diversa:
def printValue(x: Int) {
println(s"My value is an integer equal to $x")
}
def printValue(x: String) {
println(s"My value is a string equal to '$x'")
}
printValue(1) // prints "My value is an integer equal to 1"
printValue("1") // prints "My value is a string equal to '1'"
Funziona allo stesso modo se all'interno di classi, tratti, oggetti o meno.
Parametri nominati
Quando si richiama una def , i parametri possono essere assegnati esplicitamente per nome. Ciò significa che non è necessario ordinarli correttamente. Ad esempio, definire printUs() come:
// print out the three arguments in order.
def printUs(one: String, two: String, three: String) =
println(s"$one, $two, $three")
Ora può essere chiamato in questi modi (tra gli altri):
printUs("one", "two", "three")
printUs(one="one", two="two", three="three")
printUs("one", two="two", three="three")
printUs(three="three", one="one", two="two")
Ciò comporta che one, two, three vengano stampati in tutti i casi.
Se non tutti gli argomenti sono nominati, i primi argomenti sono abbinati per ordine. Nessun argomento posizionale (non denominato) può seguire un nome:
printUs("one", two="two", three="three") // prints 'one, two, three'
printUs(two="two", three="three", "one") // fails to compile: 'positional after named argument'