poniedziałek, 21 lutego 2011

Lisp - obsługa błędów

Jak po poprzednim wpisie wiadomo, ostatnio zajmuje się nauką Lispa. Aktualnie zajmuje się jeszcze czytaniem książki Practical Common Lisp którą mam zamiar w tym tygodniu skończyć i napisać coś ciekawego (zastanawiam się jeszcze co ale mam pewne pomysły). W tym wpisie postanowiłem natomiast opisać (raczek pobieżnie) mechanizm obsługi błędów w Lisp.

W każdym programie, niezależnie jak dobrze będzie napisany i jak bezpieczny będzie sam język, kiedyś wystąpią sytuację w których wystąpi jakiś błąd. Podstawowymi scenariuszami na które trzeba się przygotować to np. brak pliku który chcemy odczytać, plik zawierający błędy czy dostarczenie przez użytkownika błędnych danych. Różne języki mają inne mechanizmy do takich sytuacji. Wśród wcześniej znanych mi sposobów są:
  1. Zwrócenie błędu przez funkcję
  2. Globalna zmienna błędu pozyskiwana przez funkcję typu GetLastError
  3. Wyjątki i ich rzucanie i łapanie
W czasie nauki Lispa poznałem jeszcze jeden a mianowicie conditions and restarts (warunki i wznowienia). Czym różni się to rozwiązanie od wymienionych wcześniej? Podstawową różnicą czyniącą ten mechanizm ciekawym jest to, że zasygnalizowanie warunku (może lepiej będę pisał błędu) nie musi wiązać się z rozwijaniem stosu wywołań funkcji. Najpierw mały przykład kodu aby lepiej zilustrować o co chodzi.
;;Przykładowa klasa błędu
(define-condition some-example-error (error)
  ((text :initarg :text :reader text)))

;;Funkcja sygnalizująca błąd
(defun error-prone-func (text)
  (if (< (length text) 10)
      text
      (error 'some-example-error :text text)))

;;Funkcja udostępniające wznowienia
(defun some-func (text)
  (restart-case (error-prone-func text)
    (change-text (fixed-text)
      (error-prone-func fixed-text))
    (skip () nil)))

;;Funckja wybierająca co zrobić
(defun another-func ()
  (handler-bind ((some-example-error
    #'(lambda (c)
        (invoke-restart 'change-text (subseq (text c) 0 9)))))
    (loop for text in (list "napis1" "napis2" "napis3" "bardzo zly napis")
  when (some-func text) collect it)))
Oczywiście podany przykład nie jest zbyt sensowny ale ma demonstrować sposób działania mechanizmu obsługi błędów. O co chodzi w przykładzie: mamy funkcję error-prone-func która sygnalizuje błąd jeżeli podana kolekcja (w tym wypadku łańcuch znaków) ma więcej niż 9 elementów, następnie mamy funkcję która definiuje wznowienia dla wcześniejszej funkcji (tak właściwie to ona nie musi istnieć ale chodzi o pokazanie, że można definiować wznowienia w dowolnym miejscu). Możemy wybrać jeden z dwóch scenariuszy albo zmieniamy text aby nie powodował błędu albo pomijamy go i zwracamy nil. I na koniec jest funkcja która wywołuje całość i wybiera które wznowienie użyć (w tym wypadku zmienimy text na pierwsze 9 elementów).

Mechanizm warunków i wznowień pozwala także poza sygnalizowaniem błędów i ostrzeżeń (warn) po prostu wysłać sygnał który może nie zostać obsłużony bez żadnych konsekwencji (w wypadku error doprowadziłoby to do otworzenia debuggera a w wypadku warn wyświetlenie informacji o nie obsłużonym ostrzeżeniu).

Muszę przyznać, że ten mechanizm obsługi błędów mi się spodobał. Jest bardzo elastyczny i został również przewidziany do użytku innego niż same błędy. Zastanawiam się jak wygląda narzut tego mechanizmu i jak on ma się w porównaniu do np. mechanizmu wyjątków z C++. Może kiedyś trzeba będzie na to spojrzeć, póki co wracam do książki.

Brak komentarzy: