Faule Auswertung von Funktionsargumenten

Fehler melden

Wenn Sie ein Problem mit dieser Seite feststellen, klicken Sie hier, um ein Bugzilla-Problem zu erstellen.

Diese Seite verbessern

Forken, bearbeiten und senden Sie schnell einen Pull Request für diese Seite.Erfordert ein angemeldetes GitHub-Konto. Dies funktioniert gut für kleine Änderungen.Wenn Sie größere Änderungen vornehmen möchten, sollten Sie einen lokalen Klon verwenden.

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

Lazy evaluation ist die Technik, einen Ausdruck nicht auszuwertenunless und bis das Ergebnis des Ausdrucks erforderlich ist.Die &&, |/ und ?: Operatoren sind die herkömmliche Art und Weise faul Auswertung todo:

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

Der zweite Ausdruck p wird nur ausgewertet, wenn piist nicht null.Wenn der zweite Ausdruck nicht träge ausgewertet würde, würde er einen Laufzeitfehler erzeugen, wenn p null wäre.

Die faulen Auswerteoperatoren sind zwar von unschätzbarem Wert, weisen jedoch erhebliche Einschränkungen auf. Betrachten Sie eine Protokollierungsfunktion, die eine Nachricht protokolliert und zur Laufzeit basierend auf einem globalvalue ein- und ausgeschaltet werden kann:

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

Häufig wird die Nachrichtenzeichenfolge zur Laufzeit erstellt:

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

Während dies funktioniert, besteht das Problem darin, dass das Erstellen des Messagestring unabhängig davon erfolgt, ob die Protokollierung aktiviert ist oder nicht.Bei Anwendungen, die die Protokollierung stark nutzen, kann dies zu einer schrecklichen Belastung für die Leistung werden.

Eine Möglichkeit, das Problem zu beheben, ist die Verwendung von Lazy evaluation:

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

Dies verstößt jedoch gegen die Kapselungsprinzipien, indem dem Benutzer die Details der Protokollierung angezeigt werden. In C wird dieses Problem häufig bearbeitetmit einem Makro:

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

aber das ist nur das Problem. Präprozessor-Makros havewell bekannte Mängel:

  • Die Protokollierungsvariable wird im Namespace des Benutzers verfügbar gemacht.
  • Makros sind für symbolische Debugger unsichtbar.
  • Makros sind nur global und können nicht im Gültigkeitsbereich festgelegt werden.
  • Makros können keine Klassenmitglieder sein.
  • Makros können ihre Adresse nicht erhalten und können daher nicht indirekt wie Funktionen übergeben werden.

Eine robuste Lösung wäre eine Möglichkeit, Funktionsparameter faul auszuwerten. Dies ist in der Programmiersprache D mithilfe eines Delegatenparameters möglich:

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

Jetzt wird der String-Erstellungsausdruck nur ausgewertet, wenn loggingis true und die Kapselung beibehalten wird. Das einzige Problem ist, dasswenige werden Ausdrücke mit { return exp; } .

D geht also einen kleinen, aber entscheidenden Schritt weiter (vorgeschlagen von Andrei Alexandrescu).Jeder Ausdruck kann implizit in einen Delegaten konvertiert werden, der entweder void oder den Typ des Ausdrucks zurückgibt.Die Delegatendeklaration wird durch die von Tomasz Stachowiak vorgeschlagene Lazy Storage-Klasse ersetzt.Die Funktionen werden dann:

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

welches ist unsere ursprüngliche Version, außer dass jetzt die Zeichenfolge notconstructed ist, es sei denn, die Protokollierung ist aktiviert.

Jedes Mal, wenn ein sich wiederholendes Muster im Code zu sehen ist, bedeutet die Möglichkeit, dieses Muster herauszufiltern und zu kapseln, dass wir die Komplexität des Codes und damit die Fehler reduzieren können. Das häufigste Beispiel dafür ist die Funktionselbst.Lazy Evaluation ermöglicht die Kapselung einer Vielzahl anderer Muster.

Angenommen, ein Ausdruck soll in einem einfachen Beispiel counttimes ausgewertet werden. Das Muster ist:

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

Dieses Muster kann mithilfe einer verzögerten Auswertung in eine Funktion gekapselt werden:

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

Es kann wie verwendet werden:

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

welches wird drucken:

0123456789

Komplexere benutzerdefinierte Steuerungsstrukturen sind möglich.Hier ist eine Methode zum Erstellen einer Switch-ähnlichen Struktur:

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

welches kann wie verwendet werden:

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

welches wird drucken:

it is 2

Diejenigen, die mit der Programmiersprache Lisp vertraut sind, werden einige auffällige Parallelen zu Lisp-Makros bemerken.

Als letztes Beispiel gibt es das gemeinsame Muster:

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

Da throw eine Anweisung und kein Ausdruck ist, müssen Ausdrücke, die dies tun müssen, in mehrere Anweisungen aufgeteilt werden,und zusätzliche Variablen werden eingeführt.(Für eine gründliche Behandlung dieses Problems siehe Andrei Alexandrescu undPetru Marginean’s paperEnforcements).Mit lazy evaluation kann dies alles in eine singlefunction gekapselt werden:

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

und das obige Eröffnungsbeispiel wird einfach:

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

und 5 Zeilen Code werden eins. Es kann verbessert werden, indem man es zu einer template-Funktion macht:

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

Fazit

Die verzögerte Auswertung von Funktionsargumenten erweitert die Ausdruckskraft von Funktionen dramatisch. Es ermöglicht die Kapselung vieler gängiger Codierungsmuster und Redewendungen in Funktionen, die zuvor zu ungeschickt oder unpraktisch waren.

Danksagung

Ich danke Andrei Alexandrescu, Bartosz Milewski und David Held für ihre Inspiration und Unterstützung. Die D-Community hat viel mit konstruktiver Kritik geholfen, wie zum Beispiel dem Thread, der mit Tomasz Stachowiak in D / 41633 beginnt.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.