2013-09-26 54 views
8

Capisco il concetto base di call-by-name e call-by-value, e ho anche esaminato un numero limitato di esempi. Tuttavia, non sono molto chiaro su quando utilizzare call-by-name. Quale sarebbe uno scenario del mondo reale in cui call-by-name avrebbe un vantaggio significativo o un guadagno in termini di prestazioni rispetto all'altro tipo di chiamata? Quale dovrebbe essere l'approccio corretto per selezionare un tipo di chiamata durante la progettazione di un metodo?Quando utilizzare call-by-name e call-by-value?

+0

avete un esempio in mente? – dax

risposta

16

Ci sono molti luoghi in cui la chiamata per nome può aumentare le prestazioni o anche la correttezza.

Esempio di prestazioni semplice: registrazione. Immaginate un'interfaccia simile a questo:

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

e poi utilizzato in questo modo:

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

Se il metodo info non fa nulla (perché, per esempio, il livello di registrazione è stato configurato per più alto di quello) , 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 avete visto il codice come questo:

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

Diciamo che dichiarato && metodo come questo:

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

poi, ogni volta che è refnull, si otterrà un errore perché isSomething volontà essere chiamato su un riferimento null prima di passare a &&. Per questo motivo, la dichiarazione effettiva è:

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

Quindi ci si può meravigliare quando utilizzare call-by-value. In effetti, nel linguaggio di programmazione Haskell tutto funziona in modo simile a come funziona call-by-name (simile, ma non uguale).

Ci sono buone ragioni per non usare chiamare per nome: è più lento, si crea più classi (ovvero il programma richiede più tempo a caricarsi), consuma più memoria, ed è diverso sufficiente che molti hanno ragionamento difficile a proposito.

+0

C'è una leggera correzione qui nella descrizione precedente: Nell'esempio, anche se il livello di registro è impostato su un valore superiore al livello di informazione, verrà richiamata la funzione invocata all'interno del datalogger. Solo il messaggio del logger non verrà stampato. – Sangeeta

+0

@Sangeeta No, la funzione non sarà chiamata. Questo è il punto. –

4

Il modo semplice potrebbe essere spiegato è

funzioni call-by-value calcolano il valore passato di espressione prima di chiamare la funzione, così lo stesso valore viene letta ogni volta . Tuttavia, le funzioni call-by-name ricalcolano il valore dell'espressione passata ogni volta che viene effettuato l'accesso.

Ho sempre pensato che questa terminologia sia inutilmente confusa. Una funzione può avere più parametri che variano nel loro stato di call-by-name vs call-value. Quindi non è che una funzione sia chiamata per nome o call-by-value, è che ognuno dei suoi parametri può essere pass-by-name o pass-by-value. Inoltre, "call-by-name" non ha nulla a che fare con i nomi. => Int è un tipo diverso da Int; è "funzione di nessun argomento che genererà un Int" vs solo Int. Una volta che hai le funzioni di prima classe, non è necessario inventare la terminologia call-by-name per descriverlo.

6

chiamata dal nome significa il valore viene valutata nel momento in cui si accede, mentre con la chiamata per valore il valore viene valutata per prima e poi passato al metodo.

Per vedere la differenza, considerare questo esempio (completamente programmazione non funzionale con effetti collaterali solo;)). Supponi di voler creare una funzione che misura quanto tempo impiega un'operazione. Puoi farlo con la chiamata per nome:

def measure(action: => Unit) = { 
    println("Starting to measure time") 
    val startTime = System.nanoTime 
    action 
    val endTime = System.nanoTime 
    println("Operation took "+(endTime-startTime)+" ns") 
} 

measure { 
    println("Will now sleep a little") 
    Thread.sleep(1000) 
} 

Si otterrà il risultato (YMMV):

Starting to measure time 
Will now sleep a little 
Operation took 1000167919 ns 

Ma se si cambia solo la firma del measure-measure(action: Unit) in modo che utilizza pass per valore, il risultato sarà:

Will now sleep a little 
Starting to measure time 
Operation took 1760 ns 

Come si può vedere, la action viene valutata prima measure inizia ancora e anche il tempo trascorso è vicino a 0 a causa dell'azione già eseguita prima che il metodo venisse chiamato.

Qui, pass-by-value consente il comportamento previsto del metodo da raggiungere. In alcuni casi non influenza la correttezza, ma influenza le prestazioni, ad esempio nei quadri di registrazione in cui un'espressione complessa potrebbe non essere affatto valutata se il risultato non viene utilizzato.

0

Quando un parametro chiamata per nome viene utilizzato più di una volta in una funzione, il parametro viene valutata più di una volta.

Come il parametro viene passato in dovrebbe essere una funzione di chiamata puro per programmazione funzionale, ogni valutazione all'interno della funzione chiamata genererà sempre lo stesso risultato. Pertanto, call-by-name sarebbe più dispendioso rispetto alla chiamata convenzionale per valore.

Problemi correlati