Ci sono già molte risposte fantastiche per questa domanda in Internet. Scriverò una raccolta di diverse spiegazioni ed esempi che ho raccolto sull'argomento, nel caso qualcuno lo trovasse utile
INTRODUZIONE
call-by-value (CBV)
In genere, i parametri delle funzioni sono parametri di chiamata per valore; vale a dire, i parametri vengono valutati da sinistra a destra per determinarne il valore prima che venga valutata la funzione stessa
def first(a: Int, b: Int): Int = a
first(3 + 4, 5 + 6) // will be reduced to first(7, 5 + 6), then first(7, 11), and then 7
call-by-name (CBN)
E se avessimo bisogno di scrivere una funzione che accetta come parametro un'espressione che non dobbiamo valutare fino a quando non viene chiamata all'interno della nostra funzione? Per questa circostanza, Scala offre parametri di chiamata per nome. Significa che il parametro viene passato nella funzione così com'è e la sua valutazione avviene dopo la sostituzione
def first1(a: Int, b: => Int): Int = a
first1(3 + 4, 5 + 6) // will be reduced to (3 + 4) and then to 7
Un meccanismo di chiamata per nome passa un blocco di codice alla chiamata e ogni volta che la chiamata accede al parametro, il blocco di codice viene eseguito e il valore viene calcolato. Nell'esempio seguente, ritardato stampa un messaggio che dimostra che il metodo è stato inserito. Successivamente, in ritardo stampa un messaggio con il suo valore. Infine, i rendimenti ritardati 't':
object Demo {
def main(args: Array[String]) {
delayed(time());
}
def time() = {
println("Getting time in nano seconds")
System.nanoTime
}
def delayed( t: => Long ) = {
println("In delayed method")
println("Param: " + t)
}
}
In metodo ritardato
Ottenimento del tempo in nano secondi
Param: 2027245119786400
PRO E CONTRO PER OGNI CASO
CBN:
+ Termina più spesso * controlla sotto la terminazione sopra * + Ha il vantaggio che un argomento di funzione non viene valutato se il parametro corrispondente non viene utilizzato nella valutazione del corpo della funzione -È più lento, crea più classi (il che significa che il programma accetta più lungo da caricare) e consuma più memoria.
CBV:
+ È spesso esponenzialmente più efficiente di CBN, perché evita questo ripetuto ricalcolo degli argomenti che le espressioni chiamate per nome comportano. Valuta ogni argomento della funzione solo una volta + Gioca molto meglio con effetti imperativi ed effetti collaterali, perché tendi a conoscere molto meglio quando verranno valutate le espressioni. -Può portare ad un ciclo durante la valutazione dei suoi parametri * controllare sotto la terminazione sopra *
Cosa succede se la risoluzione non è garantita?
-Se la valutazione CBV di un'espressione e termina, allora termina anche la valutazione CBN di e -L'altra direzione non è vera
Esempio di non terminazione
def first(x:Int, y:Int)=x
Considera prima l'espressione (1, loop)
CBN: primo (1, ciclo) → 1 CBV: primo (1, ciclo) → riduce gli argomenti di questa espressione. Poiché uno è un ciclo, riduce gli argomenti in modo infinito. Non termina
DIFFERENZE IN OGNI COMPORTAMENTO DEL CASO
Definiamo un metodo di prova che sarà
Def test(x:Int, y:Int) = x * x //for call-by-value
Def test(x: => Int, y: => Int) = x * x //for call-by-name
Test Case1 (2,3)
test(2,3) → 2*2 → 4
Poiché iniziamo con argomenti già valutati, sarà lo stesso numero di passaggi per chiamata per valore e chiamata per nome
Test Case2 (3 + 4,8)
call-by-value: test(3+4,8) → test(7,8) → 7 * 7 → 49
call-by-name: (3+4)*(3+4) → 7 * (3+4) → 7 * 7 → 49
In questo caso, call-by-value esegue meno passaggi
Test Case3 (7, 2 * 4)
call-by-value: test(7, 2*4) → test(7,8) → 7 * 7 → 49
call-by-name: (7)*(7) → 49
Evitiamo il calcolo non necessario del secondo argomento
Test Case4 (3 + 4, 2 * 4)
call-by-value: test(7, 2*4) → test(7,8) → 7 * 7 → 49
call-by-name: (3+4)*(3+4) → 7*(3+4) → 7*7 → 49
Approccio diverso
Innanzitutto, supponiamo di avere una funzione con un effetto collaterale. Questa funzione stampa qualcosa e quindi restituisce un Int.
def something() = {
println("calling something")
1 // return value
}
Ora definiremo due funzioni che accettano argomenti Int esattamente identici, tranne per il fatto che uno accetta l'argomento in uno stile call-by-value (x: Int) e l'altro in uno stile call-by-name (x: => Int).
def callByValue(x: Int) = {
println("x1=" + x)
println("x2=" + x)
}
def callByName(x: => Int) = {
println("x1=" + x)
println("x2=" + x)
}
Cosa succede quando li chiamiamo con la nostra funzione di effetto collaterale?
scala> callByValue(something())
calling something
x1=1
x2=1
scala> callByName(something())
calling something
x1=1
calling something
x2=1
Quindi puoi vedere che nella versione call-by-value, l'effetto collaterale della chiamata di funzione passata (qualcosa ()) si è verificato solo una volta. Tuttavia, nella versione call-by-name, l'effetto collaterale si è verificato due volte.
Questo perché le funzioni call-by-value calcolano il valore dell'espressione passata prima di chiamare la funzione, quindi si accede sempre allo stesso valore. Tuttavia, le funzioni di chiamata per nome ricalcolano il valore dell'espressione passata ogni volta che si accede.
ESEMPI DOVE È MEGLIO UTILIZZARE CHIAMATA PER NOME
Da: https://stackoverflow.com/a/19036068/1773841
Semplice esempio di prestazioni: registrazione.
Immaginiamo un'interfaccia come questa:
trait Logger {
def info(msg: => String)
def warn(msg: => String)
def error(msg: => String)
}
E poi usato così:
logger.info("Time spent on X: " + computeTimeSpent)
Se il metodo info non fa nulla (perché, diciamo, il livello di registrazione è stato configurato per un livello superiore a quello), allora computeTimeSpent non viene mai chiamato, risparmiando tempo. Questo succede molto con i logger, dove spesso si vede la manipolazione di stringhe che può essere costosa rispetto alle attività che vengono registrate.
Esempio di correttezza: operatori logici.
Probabilmente hai visto questo codice:
if (ref != null && ref.isSomething)
Immagina di dichiarare il metodo && in questo modo:
trait Boolean {
def &&(other: Boolean): Boolean
}
quindi, ogni volta che ref è null, verrà visualizzato un errore poiché isSomething verrà chiamato su un riferimento null prima di essere passato a &&. Per questo motivo, la dichiarazione effettiva è:
trait Boolean {
def &&(other: => Boolean): Boolean =
if (this) this else other
}
=> Int
è un tipo diverso daInt
; è "funzione di nessun argomento che genererà unInt
" vs soloInt
. Una volta che hai funzioni di prima classe non è necessario inventare una terminologia chiamata per nome per descriverlo.