Qual è la differenza tra:
def even: Int => Boolean = _ % 2 == 0
e
val even: Int => Boolean = _ % 2 == 0
Entrambi possono essere chiamati come even(10)
.
Qual è la differenza tra:
def even: Int => Boolean = _ % 2 == 0
e
val even: Int => Boolean = _ % 2 == 0
Entrambi possono essere chiamati come even(10)
.
Risposte:
Il metodo def even
valuta alla chiamata e crea ogni volta una nuova funzione (nuova istanza di Function1
).
def even: Int => Boolean = _ % 2 == 0
even eq even
//Boolean = false
val even: Int => Boolean = _ % 2 == 0
even eq even
//Boolean = true
Con def
te puoi ottenere una nuova funzione ad ogni chiamata:
val test: () => Int = {
val r = util.Random.nextInt
() => r
}
test()
// Int = -1049057402
test()
// Int = -1049057402 - same result
def test: () => Int = {
val r = util.Random.nextInt
() => r
}
test()
// Int = -240885810
test()
// Int = -1002157461 - new result
val
valuta quando definito, def
- quando chiamato:
scala> val even: Int => Boolean = ???
scala.NotImplementedError: an implementation is missing
scala> def even: Int => Boolean = ???
even: Int => Boolean
scala> even
scala.NotImplementedError: an implementation is missing
Si noti che c'è una terza opzione: lazy val
.
Valuta quando viene chiamato la prima volta:
scala> lazy val even: Int => Boolean = ???
even: Int => Boolean = <lazy>
scala> even
scala.NotImplementedError: an implementation is missing
Restituisce sempre lo stesso risultato (in questo caso la stessa istanza di FunctionN
) ogni volta:
lazy val even: Int => Boolean = _ % 2 == 0
even eq even
//Boolean = true
lazy val test: () => Int = {
val r = util.Random.nextInt
() => r
}
test()
// Int = -1068569869
test()
// Int = -1068569869 - same result
Prestazione
val
valuta quando definito.
def
valuta su ogni chiamata, quindi le prestazioni potrebbero essere peggiori rispetto val
a più chiamate. Otterrai le stesse prestazioni con una sola chiamata. E senza chiamate non avrai costi generali def
, quindi puoi definirlo anche se non lo userai in alcuni rami.
Con a lazy val
otterrai una valutazione pigra: puoi definirla anche se non la userai in alcuni rami, e la valutazione una volta o mai più, ma otterrai un piccolo overhead dal doppio controllo del blocco su ogni accesso al tuo lazy val
.
Come notato da @SargeBorsch, potresti definire il metodo, e questa è l'opzione più veloce:
def even(i: Int): Boolean = i % 2 == 0
Ma se hai bisogno di una funzione (non metodo) per la composizione della funzione o per funzioni di ordine superiore (come filter(even)
) il compilatore genererà una funzione dal tuo metodo ogni volta che la usi come funzione, quindi le prestazioni potrebbero essere leggermente peggiori rispetto a val
.
even
viene chiamato.
def
può essere usato per definire un metodo, e questa è l'opzione più veloce. @ A.Karimi
even eq even
.
@inline
attributo per questo. Ma non può incorporare le funzioni perché la chiamata di funzione è una chiamata al apply
metodo virtuale di un oggetto funzione. JVM potrebbe devirtualizzare e incorporare tali chiamate in alcune situazioni, ma non in generale.
Considera questo:
scala> def even: (Int => Boolean) = {
println("def");
(x => x % 2 == 0)
}
even: Int => Boolean
scala> val even2: (Int => Boolean) = {
println("val");
(x => x % 2 == 0)
}
val //gets printed while declaration. line-4
even2: Int => Boolean = <function1>
scala> even(1)
def
res9: Boolean = false
scala> even2(1)
res10: Boolean = false
Vedi la differenza? In breve:
def : per ogni chiamata a even
, chiama even
nuovamente il corpo del metodo. Ma con even2
ie val , la funzione viene inizializzata solo una volta durante la dichiarazione (e quindi stampa val
alla riga 4 e mai più) e lo stesso output viene utilizzato ogni volta che accede. Ad esempio, prova a fare questo:
scala> import scala.util.Random
import scala.util.Random
scala> val x = { Random.nextInt }
x: Int = -1307706866
scala> x
res0: Int = -1307706866
scala> x
res1: Int = -1307706866
Quando x
viene inizializzato, il valore restituito da Random.nextInt
viene impostato come valore finale di x
. La prossima volta che x
viene usato di nuovo, restituirà sempre lo stesso valore.
Puoi anche inizializzare pigramente x
. cioè la prima volta che viene usato viene inizializzato e non durante la dichiarazione. Per esempio:
scala> lazy val y = { Random.nextInt }
y: Int = <lazy>
scala> y
res4: Int = 323930673
scala> y
res5: Int = 323930673
even2
due volte, una volta con 1
e una volta con 2
. Riceverai risposte diverse in ogni chiamata. Quindi, mentre println
non viene eseguito nelle chiamate successive, non si ottiene lo stesso risultato da chiamate diverse a even2
. Per quanto riguarda il motivo per cui println
non viene eseguito di nuovo, questa è una domanda diversa.
Guarda questo:
var x = 2 // using var as I need to change it to 3 later
val sq = x*x // evaluates right now
x = 3 // no effect! sq is already evaluated
println(sq)
Sorprendentemente, questo stamperà 4 e non 9! val (anche var) viene valutato immediatamente e assegnato.
Ora cambia val in def .. stamperà 9! Def è una chiamata di funzione .. verrà valutata ogni volta che viene chiamata.
val ie "sq" è da Scala la definizione è fissa. Viene valutato proprio al momento della dichiarazione, non è possibile modificarlo in seguito. In altri esempi, in cui even2 vale anche, ma ha dichiarato con la firma della funzione ovvero "(Int => Boolean)", quindi non è di tipo Int. È una funzione e il suo valore è impostato seguendo l'espressione
{
println("val");
(x => x % 2 == 0)
}
Come per la proprietà Scala val, non è possibile assegnare un'altra funzione a even2, stessa regola di sq.
Perché non chiamare ripetutamente la funzione val di eval2 val?
Codice orig:
val even2: (Int => Boolean) = {
println("val");
(x => x % 2 == 0)
}
Sappiamo che in Scala l'ultima affermazione del precedente tipo di espressione (dentro {..}) è in realtà tornare alla sinistra. Quindi finisci per impostare even2 su "x => x% 2 == 0", che corrisponde al tipo che hai dichiarato per even2 val type ie (Int => Boolean), quindi il compilatore è contento. Ora even2 punta solo alla funzione "(x => x% 2 == 0)" (non qualsiasi altra istruzione prima di es. Println ("val") ecc. Invocare event2 con parametri diversi in realtà invocherà "(x => x% 2 == 0) "codice, poiché solo quello viene salvato con event2.
scala> even2(2)
res7: Boolean = true
scala> even2(3)
res8: Boolean = false
Giusto per chiarire questo, di seguito è riportata una versione diversa del codice.
scala> val even2: (Int => Boolean) = {
| println("val");
| (x => {
| println("inside final fn")
| x % 2 == 0
| })
| }
Cosa accadrà ? qui vediamo "inside final fn" stampato ancora e ancora, quando chiami even2 ().
scala> even2(3)
inside final fn
res9: Boolean = false
scala> even2(2)
inside final fn
res10: Boolean = true
scala>
L'esecuzione di una definizione come def x = e
non valuterà l'espressione e. Invece e viene valutato ogni volta che viene invocato x.
In alternativa, Scala offre una definizione di valore
val x = e
, che valuta il lato destro come parte della valutazione della definizione. Se x viene quindi utilizzato successivamente, viene immediatamente sostituito dal valore pre-calcolato di e, quindi l'espressione non deve essere valutata di nuovo.
inoltre, Val è una valutazione in base al valore. Ciò significa che l'espressione sul lato destro viene valutata durante la definizione. Dove Def è per valutazione del nome. Non valuterà fino a quando non verrà utilizzato.
Oltre alle risposte utili di cui sopra, i miei risultati sono:
def test1: Int => Int = {
x => x
}
--test1: test1[] => Int => Int
def test2(): Int => Int = {
x => x+1
}
--test2: test2[]() => Int => Int
def test3(): Int = 4
--test3: test3[]() => Int
Quanto sopra mostra che "def" è un metodo (con parametri argomento zero) che restituisce un'altra funzione "Int => Int" quando viene invocato.
La conversione dei metodi in funzioni è ben spiegata qui: https://tpolecat.github.io/2014/06/09/methods-functions.html
In REPL,
scala> def even: Int => Boolean = { _% 2 == 0 }
even: Int => Boolean
scala> val even: Int => Boolean = { _% 2 == 0 }
even: Int => Boolean = $$Lambda$1157/1017502292@57a0aeb8
def significa call-by-name
, valutato su richiesta
val significa call-by-value
, valutato durante l'inizializzazione
Int => Boolean
significa? Penso che la sintassi di definizione siadef foo(bar: Baz): Bin = expr