leniwa ocena argumentów funkcji

Zgłoś błąd

jeśli zauważysz problem z tą stroną, kliknij tutaj, aby utworzyć problem z Bugzillą.

popraw tę stronę

szybko rozwidlaj, edytuj online i wyślij żądanie pull request dla tej strony.Wymaga zalogowanego konta GitHub. Działa to dobrze w przypadku małych zmian.Jeśli chcesz wprowadzić większe zmiany, możesz rozważyć użycie lokalnego klonu.

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

leniwa ewaluacja to technika nieuwzględniania wyrażenia i dopóki wynik wyrażenia nie jest wymagany.& &, / / i ?: operatorzy są konwencjonalnym sposobem na leniwą ocenę:

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

drugie wyrażenie p nie jest obliczane, chyba że pis nie jest null.Jeśli drugie wyrażenie nie było leniwie obliczane, wygenerowałoby błąd środowiska uruchomieniowego, gdyby p było równe null.

chociaż bezcenne, leniwe operatory oceny mają znaczne ograniczenia. Rozważ funkcję logowania, która komunikuje logsa i może być włączana i wyłączana w czasie wykonywania w oparciu o globalvalue:

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

często łańcuch wiadomości będzie konstruowany w czasie wykonywania:

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

chociaż to działa, problem polega na tym, że budowa messagestring dzieje się niezależnie od tego, czy logowanie jest włączone, czy nie.W przypadku aplikacji, które w dużym stopniu wykorzystują rejestrowanie, może to stać się strasznym spadkiem wydajności.

jednym ze sposobów naprawienia tego jest użycie leniwej oceny:

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

ale to narusza zasady enkapsulacji, ujawniając szczegóły logowania użytkownikowi. W języku C problem ten jest często wykonywany przy użyciu makra:

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

ale to już jest problem. Makra preprocesora mają znane niedociągnięcia:

  • zmienna logowania jest widoczna w przestrzeni nazw użytkownika.
  • makra są niewidoczne dla debugerów symbolicznych.
  • makra są tylko globalne i nie można ich skalować.
  • makra nie mogą być członkami klasy.
  • makra nie mogą mieć swojego adresu, więc nie mogą być przekazywane pośrednio jak funkcje.

solidnym rozwiązaniem byłoby wykonanie leniwej oceny parametrów funkcji. Taki sposób jest możliwy w języku programowania D przy użyciu parametru delegata:

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

teraz wyrażenie budujące łańcuch znaków zostanie obliczone tylko wtedy, gdy loggingis true i zostanie zachowana enkapsulacja. Jedynym problemem jest to, że będą chcieli zawijać wyrażenia za pomocą { return exp; }.

więc D idzie o jeden mały, ale kluczowy krok dalej (zasugerował Andrei Alexandrescu).Każde wyrażenie może być niejawnie skonwertowane na delegata, który zwróci albo void, albo typ wyrażenia.Deklarację delegata zastępuje Klasa lazy storage (sugerowana przez Tomasza Stachowiaka).Funkcje stają się wtedy:

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

która jest naszą oryginalną wersją, z tym, że teraz ciąg znaków nie jest budowany, chyba że logowanie jest włączone.

za każdym razem, gdy w kodzie pojawia się powtarzający się wzorzec, możliwość usunięcia tego wzorca i jego zamknięcia oznacza, że możemy zmniejszyć złożoność kodu, a co za tym idzie błędy. Najczęstszym tego przykładem jest funkcja sama.Leniwa ocena umożliwia enkapsulację wielu innych wzorców.

w prostym przykładzie załóżmy, że wyrażenie ma być obliczone jako counttimes. Wzór jest:

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

ten wzorzec może być zamknięty w funkcji za pomocą leniwej oceny:

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

może być używany jak:

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

który wydrukuje:

0123456789

możliwe są bardziej złożone struktury sterowania zdefiniowane przez użytkownika.Oto metoda tworzenia struktury typu 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; }}

które mogą być używane jak:

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

który wydrukuje:

it is 2

ci, którzy znają język programowania Lisp, zauważą pewne podobieństwa z makrami Lispu.

w ostatnim przykładzie jest wspólny wzorzec:

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

ponieważ throw jest instrukcją, a nie wyrażeniem, wyrażenia,które muszą to zrobić, muszą być podzielone na wiele instrukcji, A Dodatkowe zmienne są wprowadzane.(Szczegółowe omówienie tej kwestii można znaleźć w artykułach Andrei Alexandrescu ipetru Marginean).Dzięki leniwej ocenie można to wszystko zamknąć w jedną funkcję:

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

i powyższy przykład otwarcia staje się po prostu:

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

i 5 linijek kodu staje się jednym. Enforce można poprawić, tworząc funkcję atemplate:

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

wniosek

leniwa ocena argumentów funkcji dramatycznie rozszerza ekspresję funkcji. Umożliwia on enkapsulację w funkcje wielu wspólnych wzorców kodowania i idiomów, które wcześniej były zbyt niezdarne lub praktyczne, aby to zrobić.

podziękowania

z wdzięcznością przyjmuję inspirację i pomoc Andrei Alexandrescu, Bartosza Milewskiego i Davida Helda. Społeczność D bardzo pomogła w konstruktywnej krytyce, np. wątek zaczynający się od Tomasza Stachowiaka w D / 41633.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.