Ho appena imparato come funziona la valutazione pigra e mi chiedevo: perché la valutazione pigra non viene applicata in tutti i software attualmente prodotti? Perché usare ancora una valutazione entusiasta?
Ho appena imparato come funziona la valutazione pigra e mi chiedevo: perché la valutazione pigra non viene applicata in tutti i software attualmente prodotti? Perché usare ancora una valutazione entusiasta?
Risposte:
La valutazione pigra richiede un sovraccarico di contabilità: devi sapere se è stato ancora valutato e cose del genere. La valutazione desiderosa viene sempre valutata, quindi non devi saperlo. Ciò è particolarmente vero in contesti simultanei.
In secondo luogo, è banale convertire una valutazione desiderosa in una valutazione pigra impacchettandola in un oggetto funzione da chiamare in seguito, se lo si desidera.
In terzo luogo, la valutazione pigra implica una perdita di controllo. E se valutassi pigramente la lettura di un file da un disco? O hai tempo? Non è accettabile
La valutazione desiderosa può essere più efficiente e più controllabile, ed è banalmente convertita in valutazione pigra. Perché vorresti una valutazione pigra?
readFile
è esattamente ciò di cui ho bisogno. Inoltre, la conversione da valutazione pigra a desiderosa è altrettanto banale.
head [1 ..]
ti dà in un linguaggio puro valutato con entusiasmo, perché in Haskell dà 1
?
Principalmente perché il codice e lo stato pigro possono mescolarsi male e causare alcuni bug difficili da trovare. Se lo stato di un oggetto dipendente cambia, il valore dell'oggetto pigro può essere errato quando valutato. È molto meglio che il programmatore codifichi esplicitamente l'oggetto in modo che sia pigro quando sa che la situazione è appropriata.
In una nota a margine Haskell utilizza la valutazione Lazy per tutto. Ciò è possibile perché è un linguaggio funzionale e non utilizza lo stato (tranne in alcune circostanze eccezionali in cui sono chiaramente contrassegnati)
set!
di un pigro interprete di Scheme. > :(
La valutazione pigra non è sempre migliore.
I vantaggi in termini di prestazioni della valutazione lenta possono essere eccezionali, ma non è difficile evitare la maggior parte delle valutazioni non necessarie in ambienti desiderosi, sicuramente pigre lo rendono facile e completo, ma raramente la valutazione non necessaria nel codice è un grosso problema.
La cosa positiva della valutazione pigra è quando ti permette di scrivere un codice più chiaro; ottenere il decimo primo filtrando un infinito elenco di numeri naturali e prendere il decimo elemento di tale elenco è uno dei modi più concisi e chiari di procedere: (pseudocodice)
let numbers = [1,2...]
fun is_prime x = none (map (y-> x mod y == 0) [2..x-1])
let primes = filter is_prime numbers
let tenth_prime = first (take primes 10)
Credo che sarebbe abbastanza difficile esprimere le cose in modo così conciso senza pigrizia.
Ma la pigrizia non è la risposta a tutto. Per cominciare, la pigrizia non può essere applicata in modo trasparente in presenza di stato, e credo che la statualità non possa essere rilevata automaticamente (a meno che tu non stia lavorando, ad esempio, Haskell, quando lo stato è abbastanza esplicito). Quindi, nella maggior parte delle lingue, la pigrizia deve essere fatta manualmente, il che rende le cose meno chiare e quindi rimuove uno dei grandi vantaggi della valutazione pigra.
Inoltre, la pigrizia presenta degli svantaggi in termini di prestazioni, in quanto comporta un notevole sovraccarico nel mantenere le espressioni non valutate; usano l'archiviazione e sono più lenti a lavorare rispetto ai semplici valori. Non è raro scoprire che è necessario un codice entusiasta perché la versione pigra è un cane lento - ed a volte è difficile ragionare sulle prestazioni.
Dato che tende ad accadere, non esiste una strategia migliore in assoluto. Lazy è fantastico se riesci a scrivere codice migliore sfruttando infinite strutture di dati o altre strategie che ti consente di utilizzare, ma desiderare può essere più facile da ottimizzare.
Ecco un breve confronto dei pro e dei contro della valutazione desiderosa e pigra:
Valutazione desiderosa:
Potenziale sovraccarico di cose inutilmente valutabili.
Valutazione rapida e senza ostacoli.
Valutazione pigra:
Nessuna valutazione non necessaria.
Spese generali di contabilità ad ogni utilizzo di un valore.
Quindi, se hai molte espressioni che non devono mai essere valutate, pigro è meglio; ma se non hai mai un'espressione che non ha bisogno di essere valutata, pigro è puro sovraccarico.
Ora diamo un'occhiata al software del mondo reale: quante funzioni che scrivi non richiedono una valutazione di tutti i loro argomenti? Soprattutto con le moderne funzioni brevi che fanno solo una cosa, la percentuale di funzioni che rientrano in questa categoria è molto bassa. Pertanto, la valutazione lenta introdurrebbe semplicemente il sovraccarico di contabilità per la maggior parte del tempo, senza la possibilità di salvare effettivamente nulla.
Di conseguenza, la valutazione pigra semplicemente non paga in media, la valutazione desiderosa è la soluzione migliore per il codice moderno.
Come notato da @DeadMG, la valutazione pigra richiede un sovraccarico di contabilità. Questo può essere costoso rispetto alla valutazione desiderosa. Considera questa affermazione:
i = (243 * 414 + 6562 / 435.0 ) ^ 0.5 ** 3
Questo richiederà un po 'di calcolo per calcolare. Se utilizzo la valutazione lazy, devo verificare se è stata valutata ogni volta che la utilizzo. Se questo si trova all'interno di un circuito chiuso molto utilizzato, l'overhead aumenta in modo significativo, ma non ci sono vantaggi.
Con una valutazione entusiasta e un compilatore decente, la formula viene calcolata al momento della compilazione. La maggior parte degli ottimizzatori sposta l'assegnazione da eventuali loop in cui si verifica, se del caso.
La valutazione pigra è più adatta al caricamento di dati a cui si accede raramente e che ha un elevato sovraccarico da recuperare. È quindi più appropriato limitare i casi rispetto alla funzionalità principale.
In generale, è buona norma valutare il prima possibile le cose a cui si accede frequentemente. La valutazione pigra non funziona con questa pratica. Se accederai sempre a qualcosa, tutto ciò che la valutazione pigra farà è aggiungere un sovraccarico. Il costo / beneficio dell'utilizzo della valutazione lenta diminuisce man mano che si accede alla voce a cui si accede.
L'uso sempre della valutazione lenta implica anche un'ottimizzazione precoce. Questa è una cattiva pratica che spesso si traduce in un codice che è molto più complesso e costoso che altrimenti potrebbe essere il caso. Sfortunatamente, l'ottimizzazione prematura spesso porta a un codice che funziona più lentamente del codice più semplice. Fino a quando non puoi misurare l'effetto dell'ottimizzazione, è una cattiva idea ottimizzare il tuo codice.
Evitare l'ottimizzazione prematura non è in conflitto con le buone pratiche di codifica. Se non sono state applicate le buone pratiche, le ottimizzazioni iniziali possono consistere nell'applicazione di buone pratiche di codifica come lo spostamento dei calcoli dai cicli.
Se dovessimo potenzialmente valutare completamente un'espressione per determinarne il valore, la valutazione lenta può essere uno svantaggio. Supponiamo di avere un lungo elenco di valori booleani e vogliamo scoprire se sono tutti veri:
[True, True, True, ... False]
Per fare ciò dobbiamo esaminare tutti gli elementi dell'elenco, non importa quale, quindi non c'è possibilità di interrompere pigramente la valutazione. Possiamo usare una piega per determinare se tutti i valori booleani nell'elenco sono veri. Se utilizziamo una piega a destra, che utilizza la valutazione lazy, non otteniamo nessuno dei vantaggi della valutazione lazy perché dobbiamo esaminare ogni elemento nell'elenco:
foldr (&&) True [True, True, True, ... False]
> 0.27 secs
Una piega a destra sarà molto più lenta in questo caso di una piega rigorosa a sinistra, che non utilizza la valutazione pigra:
foldl' (&&) True [True, True, True, ... False]
> 0.09 secs
Il motivo è che una piega rigorosa a sinistra utilizza la ricorsione della coda, il che significa che accumula il valore di ritorno e non accumula e memorizza in memoria una grande catena di operazioni. Questo è molto più veloce della piega pigra a destra perché entrambe le funzioni devono comunque guardare l'intero elenco e la piega a destra non può usare la ricorsione della coda. Quindi, il punto è che dovresti usare tutto ciò che è meglio per l'attività da svolgere.