LISP - obsługa błędów
W powszechnej terminologii LISP wyjątki nazywane są warunkami.
W rzeczywistości warunki są bardziej ogólne niż wyjątki w tradycyjnych językach programowania, ponieważ condition reprezentuje każde wystąpienie, błąd lub nie, które mogą wpływać na różne poziomy stosu wywołań funkcji.
Mechanizm obsługi warunków w LISP-ie radzi sobie z takimi sytuacjami w taki sposób, że warunki są używane do sygnalizowania ostrzeżenia (powiedzmy przez wydrukowanie ostrzeżenia), podczas gdy kod wyższego poziomu na stosie wywołań może kontynuować swoją pracę.
System obsługi stanu w LISP składa się z trzech części -
- Sygnalizacja stanu
- Obsługa stanu
- Zrestartuj proces
Obsługa warunku
Podajmy przykład obsługi warunku wynikającego z dzielenia przez zero, aby wyjaśnić te pojęcia.
Aby poradzić sobie z chorobą, musisz wykonać następujące czynności -
Define the Condition - „Warunek to obiekt, którego klasa określa ogólny charakter warunku i którego dane instancji zawierają informacje o szczegółach konkretnych okoliczności, które doprowadziły do zasygnalizowania stanu”.
Makro określające warunek służy do definiowania warunku, który ma następującą składnię -
(define-condition condition-name (error)
((text :initarg :text :reader text))
)
Nowe obiekty warunku są tworzone za pomocą makra MAKE-CONDITION, które inicjalizuje pola nowego warunku na podstawie :initargs argument.
W naszym przykładzie poniższy kod definiuje warunek -
(define-condition on-division-by-zero (error)
((message :initarg :message :reader message))
)
Writing the Handlers- procedura obsługi warunku to kod, który jest używany do obsługi sygnalizowanego warunku. Zwykle jest napisany w jednej z funkcji wyższego poziomu, która wywołuje funkcję błędu. Po zasygnalizowaniu warunku mechanizm sygnalizacyjny wyszukuje odpowiednią procedurę obsługi na podstawie klasy warunku.
Każdy przewodnik składa się z -
- Specyfikator typu, który wskazuje typ warunku, który może obsłużyć
- Funkcja, która przyjmuje pojedynczy argument, warunek
Po zasygnalizowaniu warunku mechanizm sygnalizacji znajduje ostatnio ustanowioną procedurę obsługi, która jest zgodna z typem warunku, i wywołuje swoją funkcję.
Makro handler-caseustanawia procedurę obsługi warunków. Podstawowa forma sprawy obsługi -
(handler-case expression error-clause*)
Gdzie każda klauzula błędu ma postać -
condition-type ([var]) code)
Restarting Phase
To jest kod, który faktycznie odzyskuje twój program po błędach, a procedury obsługi warunków mogą następnie obsłużyć warunek, wywołując odpowiedni restart. Kod restartu jest zazwyczaj umieszczony w funkcjach średniego lub niskiego poziomu, a procedury obsługi warunków są umieszczane na wyższych poziomach aplikacji.
Plik handler-bindpozwala na zapewnienie funkcji restartu i pozwala na kontynuowanie na niższym poziomie funkcji bez rozwijania stosu wywołań funkcji. Innymi słowy, przepływ kontroli będzie nadal znajdował się w funkcji niższego poziomu.
Podstawowa forma handler-bind wygląda następująco -
(handler-bind (binding*) form*)
Gdzie każde wiązanie to lista następujących elementów -
- typ warunku
- funkcja obsługi jednego argumentu
Plik invoke-restart makro znajduje i wywołuje ostatnio powiązaną funkcję restartu z określoną nazwą jako argumentem.
Możesz mieć wiele ponownych uruchomień.
Przykład
W tym przykładzie demonstrujemy powyższe koncepcje, pisząc funkcję o nazwie funkcja dzielenia, która utworzy warunek błędu, jeśli argument dzielnika ma wartość zero. Mamy trzy anonimowe funkcje, które zapewniają trzy sposoby wyjścia z tego - zwracając wartość 1, wysyłając dzielnik 2 i ponownie obliczając lub zwracając 1.
Utwórz nowy plik kodu źródłowego o nazwie main.lisp i wpisz w nim następujący kod.
(define-condition on-division-by-zero (error)
((message :initarg :message :reader message))
)
(defun handle-infinity ()
(restart-case
(let ((result 0))
(setf result (division-function 10 0))
(format t "Value: ~a~%" result)
)
(just-continue () nil)
)
)
(defun division-function (value1 value2)
(restart-case
(if (/= value2 0)
(/ value1 value2)
(error 'on-division-by-zero :message "denominator is zero")
)
(return-zero () 0)
(return-value (r) r)
(recalc-using (d) (division-function value1 d))
)
)
(defun high-level-code ()
(handler-bind
(
(on-division-by-zero
#'(lambda (c)
(format t "error signaled: ~a~%" (message c))
(invoke-restart 'return-zero)
)
)
(handle-infinity)
)
)
)
(handler-bind
(
(on-division-by-zero
#'(lambda (c)
(format t "error signaled: ~a~%" (message c))
(invoke-restart 'return-value 1)
)
)
)
(handle-infinity)
)
(handler-bind
(
(on-division-by-zero
#'(lambda (c)
(format t "error signaled: ~a~%" (message c))
(invoke-restart 'recalc-using 2)
)
)
)
(handle-infinity)
)
(handler-bind
(
(on-division-by-zero
#'(lambda (c)
(format t "error signaled: ~a~%" (message c))
(invoke-restart 'just-continue)
)
)
)
(handle-infinity)
)
(format t "Done."))
Po wykonaniu kodu zwraca następujący wynik -
error signaled: denominator is zero
Value: 1
error signaled: denominator is zero
Value: 5
error signaled: denominator is zero
Done.
Oprócz „Systemu warunków”, jak omówiono powyżej, Common LISP zapewnia również różne funkcje, które mogą być wywoływane w celu sygnalizowania błędu. Obsługa błędu, gdy zostanie zasygnalizowany, jest jednak zależna od implementacji.
Funkcje sygnalizacji błędów w LISP
Poniższa tabela zawiera często używane funkcje sygnalizujące ostrzeżenia, przerwy, błędy niekrytyczne i krytyczne.
Program użytkownika określa komunikat o błędzie (ciąg znaków). Funkcje przetwarzają ten komunikat i mogą / nie mogą wyświetlać go użytkownikowi.
Komunikaty o błędach należy konstruować, stosując rozszerzenie format function, nie powinien zawierać znaku nowej linii na początku ani na końcu i nie musi wskazywać błędu, ponieważ system LISP zajmie się nimi zgodnie z preferowanym stylem.
Sr.No. | Funkcja i opis |
---|---|
1 | error ciąg formatu i pozostałe argumenty Sygnalizuje fatalny błąd. Nie można kontynuować tego rodzaju błędu; w ten sposób błąd nigdy nie wróci do wywołującego. |
2 | cerror ciąg-ciąg-formatu-błędu-ciąg-formatu i pozostałe argumenty Sygnalizuje błąd i wchodzi do debuggera. Pozwala jednak na kontynuację programu z debugera po usunięciu błędu. |
3 | warn ciąg formatu i pozostałe argumenty wypisuje komunikat o błędzie, ale normalnie nie trafia do debuggera |
4 | break& opcjonalny łańcuch formatu i pozostałe argumenty Drukuje wiadomość i trafia bezpośrednio do debuggera, bez możliwości przechwycenia przez zaprogramowane funkcje obsługi błędów |
Przykład
W tym przykładzie funkcja silnia oblicza silnię liczby; jeśli jednak argument jest ujemny, powoduje to wystąpienie błędu.
Utwórz nowy plik kodu źródłowego o nazwie main.lisp i wpisz w nim następujący kod.
(defun factorial (x)
(cond ((or (not (typep x 'integer)) (minusp x))
(error "~S is a negative number." x))
((zerop x) 1)
(t (* x (factorial (- x 1))))
)
)
(write(factorial 5))
(terpri)
(write(factorial -1))
Po wykonaniu kodu zwraca następujący wynik -
120
*** - -1 is a negative number.