DRY

Synthroid Without Prescription Inderal No Prescription Nexium For Sale Prevacid Generic Buy Elimite Online Prevacid Without Prescription Ultram No Prescription Prevacid For Sale Ultram Generic Buy Prednisone Online

Termin DRY (Don’t repeat yourself) określa filozofię programowania, która jest przeciwnością popularnej poniekąd metody kopiuj i wklej. Stosując ją należy wyłapywać wszystkie schematy, jakie pojawiają się w pisanym przez nas kodzie i uogólniać je. Najpopularniejszym na to sposobem jest używanie funkcji i klas. Lisp posiada dodatkowo możliwość definiowania makr, które pozwalają uogólniać kod na poziomie składni (w przeciwności do poziomu wykonywania programu, gdzie działają funkcje i klasy). O tym, jak bardzo język Scheme sprzyja stosowaniu DRY przekonałem się podczas testowania emacsowego modułu msf-abbrev. Jego działanie polega na dopełnianiu określonych przez nas nazw przez inne, zazwyczaj dłuższe wyrażenia. Dzięki przystępnemu interface’owi pisanie kodu momentami staje się tak proste jak wypełnianie formularzy. Korzystając z przykładowego dopełnienia zdefiniowanego przez autora modułu, po wpisaniu fopenx i wciśnięciu spacji, zostanie wklejony w aktualne miejsce kod podobny do następującego:

if ((f = fopen(“nazwa_pliku”, “r”)) == NULL)
{
    std::cerr << “ERROR: opening file”
              << ” at “ << __FILE__ << “:” << __LINE__
              << std::endl << std::flush;
    exit(1);
}

Pola, którymi różnią się wywołania fopen można wypełniać jak pola formularza, przechodząc od jednego do drugiego przy pomocy klawisza Tab. Jeżeli chcecie zobaczyć moduł w akcji, na stronie projektu dostępne jest demo.

Chociaż msf-abbrev często się przydaje, stwarza poważne zagrożenie dla zasady DRY. Podany wyżej przykład pisze się bardzo prosto, jego usunięcie lub modyfikacja jest już jednak bardzo kłopotliwa. Wyobraźcie sobie bowiem, że musicie zmienić w komunikacie błędu “ERROR” na “Error”, lub też kod wyjścia z jedynki na dwójkę. Z pomocą przyjść może edytor z dobrą obsługą operacji zastępowania, jednak samo nawet określenie, w których plikach (możliwe, że rozsianych po różnych projektach) użyliśmy tych szablonów, może być bardzo czasochłonne. Tymczasem rozwiązanie dla podanego przykładu można napisać w samym C++, trzymając w ten sposób definicję w jednym centralnym punkcie.

#define fopenx(filename, mode) \
    _fopenx(filename, mode, __FILE__, __LINE__)
 
FILE *_fopenx(char *filename, char *mode, char *file, int line) {
    FILE *f = NULL;
 
    if ((f = fopen(filename, mode)) == NULL) {
        std::cerr << “ERROR: opening file”
                  << ” at “ << file << “:” << line
                  << std::endl << std::flush;
        exit(1);
    }
 
    return f;
}

Używanie funkcji fopenx() jest bardzo proste:

f = fopenx(“nazwa_pliku”, “r”);

W ten sposób nie ma potrzeby sięgać po msf-abbrev, bo znaków do wpisania nie jest wcale aż tak wiele.

Możliwości abstrakcji w Scheme są naprawdę potężne. Dzięki temu, że funkcje są pełnoprawnymi obiektami i można nimi dowolnie manipulować, z przekazywaniem i zwracaniem włącznie, mogą służyć one nie tylko do opisu konkretnych działań (jak np. obliczenie iloczynu dwóch argumentów), ale i do ogólnych sposobów rozwiązywania szerokich klas problemów (np. metoda Newtona znajdowania pierwiastków funkcji). Dzięki czystej składni w miarę przyrostu złożoności kod nie staje się brzydki. Makra są zaś furtką do czynienia rzeczy najprostszymi jak to tylko możliwe. Przykład? Proszę bardzo. Pisząc w Scheme często korzysta się z funkcji rekurencyjnych, by jednak wywołać siebie, muszą być one nazwane. Wymaga to użycia form specjalnych define lub let, co wymusza określoną kolejność wyrażeń (najpierw definicje, potem użycie). Czasami jednak chcielibyśmy mieć możliwość zdefiniowania anonimowej funkcji, która jednocześnie wywołuje samą siebie. Inaczej - chcielibyśmy połączyć siłę form lambda i define. W Scheme istnieje już forma specjalna, która tworzy nową zagnieżdżoną przestrzeń nazw, pozwalając na umieszczenie w niej nowych definicji. Jest to let. Jej składnia jest, jak każdej formy specjalnej, bardzo prosta:

(let ((<nazwa1> <wyrazenie1>)
      (<nazwa2> <wyrazenie2>))
  <wartosc>)

Każdej podanej nazwie zostanie przyporządkowana odpowiadająca jej wartość, zaś całe wyrażenie let zwróci wartość wyrażenia podanego jako drugi argument. Ze zdefiniowanych nazw można korzystać tylko w ostatnim wyrażeniu. My definiujemy funkcję rekurencyjną, musimy więc mieć możliwość skorzystania z nazw jeszcze w samych wyrażeniach. Pozwalająca na to odmiana let nazywa się letrec.

Rozważmy prosty przykład silni. Dotychczas, by obliczyć silnię z dowolnej liczby musieliśmy zdefiniować obliczającą ją procedurę, a dopiero po tym z niej skorzystać.

(define (silnia x)
    (if (= x 0)
        1
        (* x (silnia (- x 1)))))
 
(silnia 5)

Wykorzystując letrec możemy powyższy efekt uzyskać pisząc:

((letrec ((silnia
      (lambda (x)
         (if (= x 0)
             1
             (* x (silnia (- x 1)))))))
  silnia) 5)

Ano, pięknie, jedyny mankament to wygląd. Definicja nie wygląda zbyt dobrze - słowa silnia używamy dwa razy (w definicji i następnie jako wartość wyrażenia letrec), gdy wystarczyło by raz, zaś w miejsce lambda i letrec można by wstawić pojedynczą nazwę. Spróbujmy więc to poprawić. Niestety nie da się wyabstrahować powyższego kodu do funkcji, głównie przez to, że Scheme korzysta z zasięgu leksykalnego (ang. lexical scope). Najkorzystniej byłoby, gdybyśmy mogli określone wyrażenie przekształcić w inne, jeszcze zanim nastąpi jego wykonanie. Do tego właśnie służą makra. Określmy najpierw, jak chcielibyśmy, by nasza nowa lambda wyglądała. Ja skłaniałbym się ku składni przypominającej define, tzn:

(def-lambda (<nazwa funkcji> <argumenty>)
  <zwracana wartosc>)

Chociaż wygląda identycznie, różni się od define tym, że zamiast związywać funkcję z podaną nazwą w aktualnej przestrzeni nazw, zwraca tę funkcję jako wartość. Nasz przykład z silnią uprościłby się więc do następującej postaci:

((def-lambda (silnia x)
   (if (= x 0)
       1
       (* x (silnia (- x 1))))) 5)

Tak, to wygląda o wiele ładniej. Nie wgłębiając się zbytnio w składnię, definicję makra def-lambda jest następująca:

(define-syntax def-lambda
  (syntax-rules ()
    ((_ (self . args) value)
     (letrec ((self (lambda args value)))
       self))))

Wyjaśnienie działania makr to temat na zupełnie inny wpis (a w zasadzie na kilka wpisów), chciałbym jednak zwrócić uwagę na sam efekt ich użycia. Dzięki wbudowanym w język możliwościom udało nam się uogólnić powtarzający się kod do zwartej i zrozumiałej postaci, która ma dodatkowo tę piękną właściwość, że niczym nie różni się od innych form specjalnych. W pewnym sensie zmieniliśmy więc sam język w taki sposób, by bardziej odpowiadał naszym potrzebom. Piękno tego rozwiązania bije na głowę to, co oferuje msf-abbrev. Dlatego, jeżeli tylko macie okazję, zamiast zmieniać edytor, proponuję zmienić język programowania.