Lazy Evaluation Of Function Arguments

Segnala un bug

Se si individua un problema con questa pagina, fare clic qui per creare un problema Bugzilla.

Migliora questa pagina

Forca rapidamente, modifica online e invia una richiesta di pull per questa pagina.Richiede un account GitHub con accesso. Questo funziona bene per piccoli cambiamenti.Se desideri apportare modifiche più grandi, potresti prendere in considerazione l’usoun clone locale.

di Walter Luminoso, http://www.digitalmars.com/d

La valutazione pigra è la tecnica per non valutare un’espressioneinsenza e fino a quando non è richiesto il risultato dell’espressione.Il & &, | | e ?: gli operatori sono il modo convenzionale di valutazione pigra:

void test(int* p){ if (p && p) ...}

La seconda espressione p non viene valutata a meno che pis non sia null.Se la seconda espressione non è stata valutata pigramente, lo farebbegenererebbe un errore di runtime se p fosse null.

Mentre inestimabile, gli operatori di valutazione pigri hanno significativolimitazioni. Si consideri una funzione di registrazione, che logsa messaggio, e può essere attivata e disattivata in fase di runtime, basato su un globalvalue:

void log(const(char) message){ if (logging) fwritefln(logfile, message);}

Spesso, la stringa di messaggio verrà costruito a runtime:

void foo(int i){ log("Entering foo() with i set to " ~ toString(i));}

Mentre questo funziona, il problema è che la costruzione del messagestring avviene indipendentemente dal fatto che la registrazione è abilitata o meno.Con le applicazioni che fanno un uso pesante della registrazione, questo può diventareun terribile scolo sulle prestazioni.

Un modo per risolverlo è usando la valutazione lazy:

void foo(int i){ if (logging) log("Entering foo() with i set to " ~ toString(i));}

ma questo viola i principi di incapsulamento esponendo i dettagli della registrazione all’utente. In C, questo problema viene spesso lavoratoutilizzando una macro:

#define LOG(string) (logging && log(string))

ma che solo documenti sul problema. Le macro del preprocessore hannocarenze ben note:

  • La variabile di registrazione è esposta nello spazio dei nomi dell’utente.
  • Le macro sono invisibili ai debugger simbolici.
  • Le macro sono solo globali e non possono essere definite nell’ambito.
  • Le macro non possono essere membri della classe.
  • Le macro non possono avere il loro indirizzo preso,quindi non possono essere passate indirettamente come le funzioni possono.

Una soluzione robusta sarebbe un modo per fare una valutazione pigra dei parametri di funzione. Tale modoè possibile nel linguaggio di programmazione D utilizzando un parametro delegato:

void log(const(char) delegate() dg){ if (logging) fwritefln(logfile, dg());}void foo(int i){ log( { return "Entering foo() with i set to " ~ toString(i); });}

Ora, l’espressione di creazione di stringhe viene valutata solo se loggingis true e l’incapsulamento viene mantenuto. L’unico problema è thatfew vorrà avvolgere le espressioni con { return exp; }.

Così D prende un piccolo, ma cruciale, passo avanti (suggerito da Andrei Alexandrescu).Qualsiasi espressione può essere convertita implicitamente in un delegato che restituisce void o il tipo di espressione.La dichiarazione del delegato viene sostituita dalla classe di archiviazione lazy(suggerita da Tomasz Stachowiak).Le funzioni diventano quindi:

void log(lazy const(char) dg){ if (logging) fwritefln(logfile, dg());}void foo(int i){ log("Entering foo() with i set to " ~ toString(i));}

che è la nostra versione originale, tranne che ora la stringa non ècostruita a meno che la registrazione non sia attivata.

Ogni volta che c’è un pattern ripetuto visto nel codice, essere in grado di astrarre quel pattern e incapsularlo significa che possiamo ridurre la complessità del codice e quindi i bug. L’esempio più comune diquesta è la funzionestesso.La valutazione Lazy consente l’incapsulamento di una serie di altri modelli.

Per un semplice esempio, supponiamo che un’espressione debba essere valutata counttimes. Il modello è:

for (int i = 0; i < count; i++) exp;

Questo modello può essere incapsulato in una funzione utilizzando la valutazione lazy:

void dotimes(int count, lazy void exp){ for (int i = 0; i < count; i++) exp();}

Può essere usato come:

void foo(){ int x = 0; dotimes(10, write(x++));}

che stamperà:

0123456789

Sono possibili strutture di controllo definite dall’utente più complesse.Ecco un metodo per creare una struttura simile a switch:

bool scase(bool b, lazy void dg){ if (b) dg(); return b;}/* Here the variadic arguments are converted to delegates in this special case. */void cond(bool delegate() cases ...){ foreach (c; cases) { if (c()) break; }}

che può essere usato come:

void foo(){ int v = 2; cond ( scase(v == 1, writeln("it is 1")), scase(v == 2, writeln("it is 2")), scase(v == 3, writeln("it is 3")), scase(true, writeln("it is the default")) );}

che stamperà:

it is 2

Chi ha familiarità con il linguaggio di programmazione Lisp noterà alcuni paralleli con le macro Lisp.

Per un ultimo esempio, c’è il modello comune:

Abc p;p = foo();if (!p) throw new Exception("foo() failed");p.bar(); // now use p

Poiché throw è un’istruzione, non un’espressione, le espressioni che devono essere suddivise in più istruzioni e vengono introdotte variabili aggiuntive.(Per un trattamento approfondito di questo problema, vedere Andrei Alexandrescu andPetru Marginean’s paperEnforcements).Con la valutazione pigra, tutto questo può essere incapsulato in una singola funzione:

Abc Enforce(Abc p, lazy const(char) msg){ if (!p) throw new Exception(msg()); return p;}

e l’esempio di apertura sopra diventa semplicemente:

Enforce(foo(), "foo() failed").bar();

e 5 righe di codice diventano una. Applicare può essere migliorato rendendolo funzione atemplate:

T Enforce(T)(T p, lazy const(char) msg){ if (!p) throw new Exception(msg()); return p;}

Conclusione

La valutazione pigra degli argomenti delle funzioni estende drasticamente il potere espressivo delle funzioni. Consente l’incapsulamento in funzioni di molti modelli di codifica comuni e idiomi che in precedenza erano troppo goffi o pratici da fare.

Ringraziamenti

Riconosco con gratitudine l’ispirazione e l’assistenza di Andrei Alexandrescu, Bartosz Milewski e David Held. La comunità D ha aiutato molto con molte critiche costruttive, come il thread che inizia con Tomasz Stachowiak in D/41633.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.