Lazy vyhodnocení argumentů funkce

Nahlásit chybu

pokud zjistíte problém s touto stránkou, klikněte zde pro vytvoření problému Bugzilla.

Vylepšete tuto stránku

rychle rozvětvte, upravte online a odešlete žádost o stažení této stránky.Vyžaduje přihlášený účet GitHub. To funguje dobře pro malé změny.Pokud chcete provést větší změny, možná budete chtít zvážit použitímístní klon.

autor: Walter Bright, http://www.digitalmars.com/d

Lazy hodnocení je technika nehodnocení výrazubezcenné a dokud není vyžadován výsledek výrazu.&&, | / a ?: operátoři jsou obvyklým způsobem líného hodnocení:

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

druhý výraz p není hodnocen, pokud pi není null.Pokud by druhý výraz nebyl líně vyhodnocen, generoval by runtime chybu, pokud by p byl null.

i když je to neocenitelné, operátoři líného hodnocení mají významné omezení. Zvažte funkci protokolování, která protokoluje zprávu, a lze ji zapnout a vypnout za běhu na základě globalvalue:

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

řetězec zpráv bude často vytvořen za běhu:

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

i když to funguje, problém je v tom, že budování messagestring se děje bez ohledu na to, zda je protokolování povoleno nebo ne.S aplikacemi, které těžce využívají protokolování, se to může státstrašný odtok výkonu.

jedním ze způsobů, jak to opravit, je použití líného hodnocení:

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

ale to porušuje principy zapouzdření tím, že vystaví detailsof protokolování uživateli. V C se tento problém často zpracovávápomocí makra:

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

ale to je jen otázka problému. Makra preprocesoru majídobře známé nedostatky:

  • proměnná protokolování je vystavena v jmenném prostoru uživatele.
  • makra jsou pro symbolické debuggery neviditelná.
  • makra jsou pouze globální a nemohou být rozsahem.
  • makra nemohou být členy třídy.
  • makra nemohou mít svou adresu, takže nemohou být předávány nepřímo, jako funkce mohou.

robustním řešením by byl způsob, jak provést líné vyhodnocení funkčních parametrů. Takový způsob je možný v programovacím jazyce D pomocí parametru delegate:

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); });}

nyní se výraz budovy řetězce vyhodnotí pouze tehdy, pokud je loggingis true, a zapouzdření je zachováno. Jediným problémem je, žemálo bude chtít zabalit výrazy pomocí { return exp;}.

takže D to vezme o jeden malý, ale zásadní krok dále (navrhl Andrei Alexandrescu).Jakýkoli výrazmůže být implicitně převeden na delegáta, který vrací buď neplatný nebo typ výrazu.Prohlášení delegáta je nahrazeno třídou lazy storage (navrhl Tomasz Stachowiak).Funkce se pak stanou:

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

což je naše původní verze, kromě toho, že nyní řetězec neníkonstruován, pokud není zapnuto protokolování.

kdykoli je v kódu vidět opakující se vzor, který je schopen tento vzor odhalit a zapouzdřit, znamená to, že můžeme snížit složitost kódu, a tím i chyby. Nejběžnějším příklademto je funkcesám.Lazy vyhodnocení umožňuje zapouzdření řady dalších vzorů.

pro jednoduchý příklad předpokládejme, že výraz má být vyhodnocen počty. Vzor je:

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

tento vzor lze zapouzdřit do funkce pomocí lazy evaluation:

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

může být použit jako:

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

který bude tisknout:

0123456789

jsou možné složitější uživatelsky definované řídicí struktury.Zde je způsob, jak vytvořit strukturu podobnou přepínači:

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; }}

které lze použít jako:

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")) );}

který bude tisknout:

it is 2

ti, kteří jsou obeznámeni s programovacím jazykem Lisp, si všimnou některých paralel s makry Lisp.

pro poslední příklad existuje společný vzor:

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

protože throw je příkaz, Ne výraz, výrazy, kterémusíte to udělat, je třeba rozdělit na více příkazů a zavést další proměnné.(Pro důkladné zpracování této problematiky, viz Andrei Alexandrescu andpetr Marginean ‚ s paperEnforcements).S líným hodnocením, to vše může být zapouzdřeno do jediné funkce:

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

a výše uvedený příklad otevření se stává jednoduše:

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

a 5 řádků kódu se stane jedním. Prosadit lze zlepšit tím, že funkce atemplate:

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

závěr

Lazy vyhodnocení funkčních argumentů dramaticky rozšiřuje expresivnívýkon funkcí. Umožňuje zapouzdření do funkcí mnoha běžných kódovacích vzorů a idiomů, které byly dříve příliš nemotorné nebo praktické.

poděkování

vděčně uznávám inspiraci a pomoc Andreje Alexandrescu, Bartosze Milewského a Davida Helda. Komunita D hodně pomohla s hodně konstruktivní kritikou, jako je vlákno začínající Tomaszem Stachowiakem v D / 41633.

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna.