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.