Chiama per nome o chiama per valore in Scala, chiarimento necessario

A quanto ho capito, in Scala, può essere chiamata una funzione

  • di valore o
  • per nome

Ad esempio, date le seguenti dichiarazioni, sappiamo come verrà chiamata la funzione?

Dichiarazione:

def f (x:Int, y:Int) = x; 

Chiamata

 f (1,2) f (23+55,5) f (12+3, 44*11) 

Quali sono le regole per favore?

L’esempio che hai dato utilizza solo call-by-value, quindi darò un nuovo, più semplice, esempio che mostra la differenza.

Innanzitutto, supponiamo di avere una funzione con un effetto collaterale. Questa funzione stampa qualcosa fuori e poi restituisce un Int .

 def something() = { println("calling something") 1 // return value } 

Ora definiremo due funzioni che accettano gli argomenti di Int che sono esattamente gli stessi eccetto che si prende 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) } 

Ora 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 della funzione passata ( something() ) si è verificato solo una volta. Tuttavia, nella versione call-by-name, l’effetto collaterale è accaduto due volte.

Questo perché le funzioni call-by-value calcolano il valore dell’espressione passata prima di chiamare la funzione, pertanto lo stesso valore viene richiamato ogni volta. Tuttavia, le funzioni call-by-name ricalcolano il valore dell’espressione passata ogni volta che viene effettuato l’accesso.

Ecco un esempio di Martin Odersky:

 def test (x:Int, y: Int)= x*x 

Vogliamo esaminare la strategia di valutazione e determinare quale sia più veloce (meno passaggi) in queste condizioni:

 test (2,3) 

call by value: test (2,3) -> 2 * 2 -> 4
chiama per nome: test (2,3) -> 2 * 2 -> 4
Qui il risultato è raggiunto con lo stesso numero di passaggi.

 test (3+4,8) 

call by value: test (7,8) -> 7 * 7 -> 49
chiamare per nome: (3 + 4) (3 + 4) -> 7 (3 + 4) -> 7 * 7 -> 49
Qui la chiamata per valore è più veloce.

 test (7,2*4) 

call by value: test (7,8) -> 7 * 7 -> 49
chiama per nome: 7 * 7 -> 49
Qui la chiamata per nome è più veloce

 test (3+4, 2*4) 

call by value: test (7,2 * 4) -> test (7, 8) -> 7 * 7 -> 49
chiamare per nome: (3 + 4) (3 + 4) -> 7 (3 + 4) -> 7 * 7 -> 49
Il risultato è raggiunto all’interno degli stessi passaggi.

Nel caso del tuo esempio tutti i parametri saranno valutati prima di essere richiamati nella funzione, poiché li stai definendo solo in base al valore . Se vuoi definire i tuoi parametri per nome, devi passare un blocco di codice:

 def f(x: => Int, y:Int) = x 

In questo modo il parametro x non verrà valutato fino a quando non viene chiamato nella funzione.

Questo piccolo post qui lo spiega molto bene.

Per iterare il punto @ Ben nei commenti sopra, penso che sia meglio pensare a “call-by-name” come semplice zucchero sintattico. Il parser racchiude semplicemente le espressioni in funzioni anonime, in modo che possano essere richiamate in un secondo momento, quando vengono utilizzate.

In effetti, invece di definire

 def callByName(x: => Int) = { println("x1=" + x) println("x2=" + x) } 

e in esecuzione:

 scala> callByName(something()) calling something x1=1 calling something x2=1 

Potresti anche scrivere:

 def callAlsoByName(x: () => Int) = { println("x1=" + x()) println("x2=" + x()) } 

E eseguilo come segue per lo stesso effetto:

 callAlsoByName(() => {something()}) calling something x1=1 calling something x2=1 

Cercherò di spiegare con un caso d’uso semplice piuttosto che solo fornendo un esempio

Immagina di voler creare una “app nagger” che ti farà impazzire ogni volta che da quando sei scorso ti sei tormentato.

Esamina le seguenti implementazioni:

 object main { def main(args: Array[String]) { def onTime(time: Long) { while(time != time) println("Time to Nag!") println("no nags for you!") } def onRealtime(time: => Long) { while(time != time) println("Realtime Nagging executed!") } onTime(System.nanoTime()) onRealtime(System.nanoTime()) } } 

Nell’implementazione di cui sopra il nagger funzionerà solo quando si passa per nome il motivo è che, passando di valore, verrà riutilizzato e quindi il valore non verrà rivalutato mentre quando si passa per nome il valore verrà rivalutato ogni tempo in cui si accede alle variabili

In genere, i parametri delle funzioni sono parametri di valore; cioè, il valore del parametro viene determinato prima che venga passato alla funzione. Ma cosa succede se abbiamo bisogno di scrivere una funzione che accetta come parametro un’espressione che non vogliamo valutare fino a quando non viene chiamata all’interno della nostra funzione? Per questa circostanza, Scala offre parametri call-by-name.

Un meccanismo call-by-name passa un blocco di codice al destinatario e ogni volta che il destinatario accede al parametro, il blocco di codice viene eseguito e il valore viene calcolato.

 object Test { 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) t } } 
  1. C: /> scalac Test.scala 
  2. scala test
  3. In metodo ritardato
  4. Ottenere il tempo in nano secondi
  5. Param: 81303808765843
  6. Ottenere il tempo in nano secondi

Come suppongo, la funzione call-by-value come discusso sopra passi solo i valori alla funzione. Secondo Martin Odersky è una strategia di valutazione seguita da una Scala che svolge un ruolo importante nella valutazione delle funzioni. Ma, semplificare la call-by-name . è come passare la funzione come argomento al metodo anche come Higher-Order-Functions . Quando il metodo accede al valore del parametro passato, richiama l’implementazione delle funzioni passate. come sotto:

Secondo l’esempio di @dhg, creare il metodo prima come:

 def something() = { println("calling something") 1 // return value } 

Questa funzione contiene una dichiarazione println e restituisce un valore intero. Crea la funzione, che ha argomenti come call-by-name :

 def callByName(x: => Int) = { println("x1=" + x) println("x2=" + x) } 

Questo parametro di funzione definisce una funzione anonima che ha restituito un valore intero. In questa x contengono una definizione di funzione che ha 0 argomenti passati ma restituisce un valore int e la nostra funzione something contiene la stessa firma. Quando chiamiamo la funzione, passiamo la funzione come argomento per callByName . Ma nel caso di call-by-value passa solo il valore intero alla funzione. Chiamiamo la funzione come di seguito:

 scala> callByName(something()) calling something x1=1 calling something x2=1 

In questo nostro metodo something chiamato due volte, perché quando accediamo al valore di x nel metodo callByName , chiamiamo la definizione di something metodo.

Call by value è un caso di uso generale come spiegato da molte risposte qui ..

Call-by-name passa un blocco di codice al chiamante e ogni volta che il chiamante accede al parametro, il blocco di codice viene eseguito e il valore viene calcolato.

Proverò a dimostrare la chiamata per nome in modo più semplice con i casi d’uso riportati di seguito

Esempio 1:

Esempio semplice / caso d’uso della chiamata per nome è sotto la funzione, che prende la funzione di parametro e dà il tempo trascorso.

  /** * Executes some code block and prints to stdout the time taken to execute the block for interactive testing and debugging. */ def time[T](f: => T): T = { val start = System.nanoTime() val ret = f val end = System.nanoTime() println(s"Time taken: ${(end - start) / 1000 / 1000} ms") ret } 

Esempio 2:

apache spark (con scala) usa il logging usando call by name way vedi Logging in cui pigramente valuta se log.isInfoEnabled o meno dal metodo sottostante.

 protected def logInfo(msg: => String) { if (log.isInfoEnabled) log.info(msg) } 

I parametri di solito passano per valore, il che significa che verranno valutati prima di essere sostituiti nel corpo della funzione.

È ansible forzare un parametro ad essere chiamato per nome utilizzando la doppia freccia quando si definisce la funzione.

 // first parameter will be call by value, second call by name, using `=>` def returnOne(x: Int, y: => Int): Int = 1 // to demonstrate the benefits of call by name, create an infinite recursion def loop(x: Int): Int = loop(x) // will return one, since `loop(2)` is passed by name so no evaluated returnOne(2, loop(2)) // will not terminate, since loop(2) will evaluate. returnOne(loop(2), 2) // -> returnOne(loop(2), 2) -> returnOne(loop(2), 2) -> ... 

Ci sono già molte risposte fantastiche per questa domanda su Internet. Scriverò una raccolta di diverse spiegazioni ed esempi che ho raccolto sull’argomento, nel caso qualcuno possa trovarlo utile

INTRODUZIONE

call-by-value (CBV)

In genere, i parametri delle funzioni sono parametri call-by-value; cioè, i parametri vengono valutati da sinistra a destra per determinare il loro valore prima che la funzione stessa venga valutata

 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)

Ma cosa succede se abbiamo 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 call-by-name. Significa che il parametro è passato alla 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 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, in ritardo viene stampato un messaggio che dimostra che il metodo è stato inserito. Successivamente, ritardato stampa un messaggio con il suo valore. Infine, restituzioni ritardate ‘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) } } 

Nel metodo ritardato
Ottenere il tempo in nano secondi
Param: 2027245119786400

PRO E CONTRO 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 è utilizzato nella valutazione del corpo della funzione -E ‘più lento, crea più classi (cioè il programma prende più lungo da caricare) e consuma più memoria.

CBV: + Spesso è esponenzialmente più efficiente di CBN, perché evita questo ripetuto ricalcolo di espressioni di argomenti che la chiamata per nome comporta. Valuta ogni argomento di funzione solo una volta + Suona molto meglio con effetti imperativi ed effetti collaterali, perché tendi a sapere molto meglio quando verranno valutate le espressioni. -Può portare a un ciclo durante la valutazione dei parametri * controllare di seguito sopra la terminazione *

Cosa succede se la terminazione non è garantita?

-Se la valutazione CBV di un’espressione e termina, allora anche la valutazione CBN di e termina-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, loop) → 1 CBV: primo (1, loop) → riduce gli argomenti di questa espressione. Poiché uno è un ciclo, riduce gli argomenti all’infinito. Non termina

DIFFERENZE IN OGNI CASO DI COMPORTAMENTO

Definiamo un test di metodo 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à la stessa quantità di passaggi per call-by-value e call-by-name

Test di 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 di 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 fuori e poi restituisce un Int.

 def something() = { println("calling something") 1 // return value } 

Ora definiremo due funzioni che accettano gli argomenti di Int che sono esattamente gli stessi eccetto che si prende 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) } 

Ora 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 della funzione passata (qualcosa ()) si è verificato solo una volta. Tuttavia, nella versione call-by-name, l’effetto collaterale è accaduto due volte.

Questo perché le funzioni call-by-value calcolano il valore dell’espressione passata prima di chiamare la funzione, pertanto lo stesso valore viene richiamato ogni volta. Tuttavia, le funzioni call-by-name ricalcolano il valore dell’espressione passata ogni volta che viene effettuato l’accesso.

ESEMPI DOVE È MIGLIORE UTILIZZARE CALL-BY-NAME

Da: https://stackoverflow.com/a/19036068/1773841

Esempio di prestazioni semplice: registrazione.

Immaginiamo un’interfaccia come questa:

 trait Logger { def info(msg: => String) def warn(msg: => String) def error(msg: => String) } 

E poi usato in questo modo:

 logger.info("Time spent on X: " + computeTimeSpent) 

Se il metodo info non fa nulla (perché, ad esempio, il livello di registrazione è stato configurato per un livello superiore), quindi computeTimeSpent non viene mai chiamato, risparmiando tempo. Questo accade molto con i logger, dove spesso si osserva una manipolazione delle stringhe che può essere costosa rispetto alle attività registrate.

Esempio di correttezza: operatori logici.

Probabilmente hai visto un codice come questo:

 if (ref != null && ref.isSomething) 

Immagina di dichiarare il metodo && in questo modo:

 trait Boolean { def &&(other: Boolean): Boolean } 

quindi, ogni volta che ref è nullo, verrà visualizzato un errore perché isSomething verrà chiamato su nullreference prima di essere passato a &&. Per questo motivo, la dichiarazione effettiva è:

 trait Boolean { def &&(other: => Boolean): Boolean = if (this) this else other } 

Passare attraverso un esempio dovrebbe aiutarti a capire meglio la differenza.

Definiamo una semplice funzione che restituisce l’ora corrente:

 def getTime = System.currentTimeMillis 

Ora definiremo una funzione, per nome , che stampa due volte in ritardo di un secondo:

 def getTimeByName(f: => Long) = { println(f); Thread.sleep(1000); println(f)} 

E uno per valore :

 def getTimeByValue(f: Long) = { println(f); Thread.sleep(1000); println(f)} 

Ora chiamiamo ciascuno:

 getTimeByName(getTime) // prints: // 1514451008323 // 1514451009325 getTimeByValue(getTime) // prints: // 1514451024846 // 1514451024846 

Il risultato dovrebbe spiegare la differenza. Lo snippet è disponibile qui .

In una Call by Value , il valore dell’espressione è pre-calcolato al momento della chiamata alla funzione e quel particolare valore viene passato come parametro alla funzione corrispondente. Lo stesso valore sarà usato per tutta la funzione.

Mentre in Call by Name , l’espressione stessa viene passata come parametro alla funzione e viene calcasting solo all’interno della funzione, ogni volta che viene chiamato quel particolare parametro.

La differenza tra Call by Name e Call by Value in Scala potrebbe essere meglio compresa con l’esempio seguente:

Snippet di codice

 object CallbyExample extends App { // function definition of call by value def CallbyValue(x: Long): Unit = { println("The current system time via CBV: " + x); println("The current system time via CBV " + x); } // function definition of call by name def CallbyName(x: => Long): Unit = { println("The current system time via CBN: " + x); println("The current system time via CBN: " + x); } // function call CallbyValue(System.nanoTime()); println("\n") CallbyName(System.nanoTime()); } 

Produzione

 The current system time via CBV: 1153969332591521 The current system time via CBV 1153969332591521 The current system time via CBN: 1153969336749571 The current system time via CBN: 1153969336856589 

Nel frammento di codice sopra, per la funzione chiama CallbyValue (System.nanoTime ()) , il sistema nano time viene calcolato in anticipo e il valore precalcolato è stato passato un parametro alla chiamata di funzione.

Ma nella chiamata alla funzione CallbyName (System.nanoTime ()) , l’espressione “System.nanoTime ())” è passata come parametro alla chiamata della funzione e il valore di tale espressione viene calcolato quando tale parametro viene utilizzato all’interno della funzione .

Si noti la definizione della funzione della funzione CallbyName, in cui è presente un simbolo => che separa il parametro x e il relativo tipo di dati. Quel particolare simbolo indica che la funzione è di chiamata per tipo di nome.

In altre parole, gli argomenti della funzione call by value vengono valutati una volta prima di immettere la funzione, ma gli argomenti della funzione call by name vengono valutati all’interno della funzione solo quando sono necessari.

Spero che questo ti aiuti!

CallByName viene invocato quando viene utilizzato e callByValue viene richiamato ogni volta che viene rilevata l’istruzione.

Per esempio:-

Ho un ciclo infinito, cioè se si esegue questa funzione non avremo mai il prompt scala .

 scala> def loop(x:Int) :Int = loop(x-1) loop: (x: Int)Int 

una funzione callByName accetta il metodo loop sopra come argomento e non viene mai utilizzato all’interno del suo corpo.

 scala> def callByName(x:Int,y: => Int)=x callByName: (x: Int, y: => Int)Int 

Durante l’esecuzione del metodo callByName non troviamo alcun problema (riceviamo il prompt scala indietro) poiché non ci sono dove usare la funzione loop all’interno callByName funzione callByName .

 scala> callByName(1,loop(10)) res1: Int = 1 scala> 

una funzione callByValue riprende il metodo loop sopra come parametro come risultato nella funzione o nell’espressione viene valutata prima dell’esecuzione della funzione esterna in base alla funzione del loop eseguita in modo ricorsivo e non viene mai richiamato lo scala .

 scala> def callByValue(x:Int,y:Int) = x callByValue: (x: Int, y: Int)Int scala> callByValue(1,loop(1)) 

Guarda questo:

  object NameVsVal extends App { def mul(x: Int, y: => Int) : Int = { println("mul") x * y } def add(x: Int, y: Int): Int = { println("add") x + y } println(mul(3, add(2, 1))) } 

y: => Int è la chiamata per nome. Ciò che viene passato come chiamata per nome è add (2, 1). Questo sarà valutato pigramente. Quindi l’output sulla console sarà “mul” seguito da “add”, anche se l’aggiunta sembra essere chiamata prima. La chiamata per nome funge da tipo di passaggio di un puntatore a funzione.
Ora cambia da y: => Int a y: Int. La console mostrerà “aggiungi” seguito da “mul”! Modo usuale di valutazione.

Non penso che tutte le risposte qui facciano la giustificazione corretta:

In call by value gli argomenti vengono calcolati una sola volta:

 def f(x : Int, y :Int) = x // following the substitution model f(12 + 3, 4 * 11) f(15, 4194304) 15 

puoi vedere sopra che tutti gli argomenti sono valutati se necessario, no, normalmente call-by-value può essere veloce ma non sempre come in questo caso.

Se la strategia di valutazione fosse call-by-name la scomposizione sarebbe stata:

 f(12 + 3, 4 * 11) 12 + 3 15 

come puoi vedere sopra non abbiamo mai avuto bisogno di valutare 4 * 11 e quindi abbiamo risparmiato un po ‘di calcolo che a volte può essere utile.