Обработка ошибок в LISP

Опубликовано: 10 Января, 2023

Система условий в Лиспе — одна из его лучших особенностей. Он выполняет ту же задачу, что и механизмы обработки исключений в Java, Python и C++, но более гибок. На самом деле его адаптируемость выходит за рамки обработки ошибок, поскольку условия, более гибкие, чем исключения, могут представлять любое событие, происходящее во время работы программы, и могут представлять интерес для кода на разных уровнях стека вызовов. Система условий более гибкая, чем системы исключений, потому что она делит обязанности на три части: сигнализация условия, его обработка и перезапуск. Системы исключений делят обязанности на две части: код, который обрабатывает проблему, и код, который сигнализирует об условии.

Условие — это объект, класс которого описывает его общие характеристики, а данные экземпляра которого содержат информацию о специфике событий, приведших к указанному условию. За исключением того факта, что надклассом по умолчанию для классов, определенных с помощью DEFINE-CONDITION, является CONDITION, а не STANDARD-OBJECT, классы условий определяются с помощью макроса DEFINE-CONDITION.

Чтобы определить условие:

Синтаксис:

(define-condition condition-name (error)
  ((text :initarg :text :reader text))
)

Отлов любого условия (обработчик-случай):

Код, используемый для обработки ситуации, о которой сообщается, называется обработчиком условий. Функция ошибки обычно вызывается из одной из функций более высокого уровня, где она обычно определена. Механизм сигнализации ищет подходящий обработчик при уведомлении о состоянии на основе класса условия. Каждый обработчик имеет спецификатор типа, который указывает ситуации, которые он может обрабатывать, а также функцию, которая принимает только условие в качестве своего единственного аргумента.

Механизм сигнализации находит самый последний обработчик, совместимый с типом условия, и вызывает его функцию всякий раз, когда сигнализируется условие. Обработчик условия создается макросом Handler-case.

Синтаксис:

(handler-case (code that errors out)
  (condition-type (the-condition)       

 ;; <– optional argument
     (code))                                          

;;<– code of error clause
  ( another-condition (the-condition)
    …))

Случай-обработчик возвращает свое значение, если код, обнаруживший ошибку, возвращается нормально. Случай Handler-body должен состоять из одного выражения; PROGN можно использовать для объединения множества выражений в одну форму. Код в соответствующем предложении ошибки выполняется, и его значение возвращается случаем обработчика, если код, который не удается выполнить, сигнализирует об условии, которое является экземпляром любого из типов условий, перечисленных в любом предложении ошибки.

Отлов определенного состояния:

Написав тип условия, мы можем сообщить обработчикам условий, какое условие обрабатывать. Этот процесс сравним с try/catch в других языках программирования, но мы можем добиться большего.

Пример 1:

Lisp




;; LISP code for error handling
(handler-case (/ 3 0)
(division-by-zero (c)
(format t "Caught division by zero: ~a~%" c)))

Выход:

Вы часто будете сталкиваться со следующим предупреждением компилятора, если сохраните объект условия в качестве аргумента, но не получите к нему доступ в своих обработчиках:

; caught STYLE-WARNING:
; The variable C is defined but never used.

Используйте вызов declare, как показано ниже, чтобы удалить его:

(handler-case (/ 3 0)
 (division-by-zero (c)
 (declare (ignore c))

(format t “Caught division by zero~%”)))

;; we don’t print “c” here and don’t get the warning.

Условия обработки (Handler-bind):

Обработчики условий могут реагировать на условие, активируя соответствующий перезапуск на этапе перезапуска, который является кодом, который действительно восстанавливает ваше приложение после проблем. Код перезапуска обычно вставляется в функции низкого или среднего уровня, а обработчики условий располагаются на более высоких уровнях приложения. Используя макрос Handler-bind, вы можете продолжить работу с функциями более низкого уровня, не раскручивая стек вызовов функций и не предоставляя функцию перезапуска. Другими словами, функция нижнего уровня будет по-прежнему контролировать процесс. Синтаксис Handler-bind следующий:

(handler-bind ((a-condition #’function-to-handle-it)         

;;<– binding
 (another-one #’another-function))
 (code that can…)
 (…error out))

Каждая привязка имеет функцию-обработчик с одним аргументом и типом условия. Макрос invoke-restart ищет и вызывает самую последнюю связанную функцию перезапуска, передавая указанное имя в качестве аргумента.

Пример 2:

Lisp




;Lisp program to demonstrate defining 
; a condition and then handling it using handler-bind
  
;If the divisor argument is zero, 
; the program will produce an error situation.
;there are three anonymous functions(cases) 
; offer three different strategies to overcome it.
; ( return 0, recalculate using divisor 3 
; and continue without returning)
  
  
(define-condition dividing-by-zero (error)
   ((message :initarg :message :reader message))
)
     
(defun div-zero-handle ()
   (restart-case
      (let ((result 0))
         (setf result (div-func 24 0))
         (format t "The value returned is: ~a~%" result)
      )
      (continue () nil)
   )
)
       
(defun div-func (val1 val2)
   (restart-case
      (if (/= val2 0)
         (/ val1 val2)
         (error "dividing-by-zero :message "denominator is zero")
      )
  
      (return-zero () 0)
      (return-val (x) x)
      (recalculate-with (s) (div-func val1 s))
   )
)
  
(defun high-level-code ()
   (handler-bind
      (
         (dividing-by-zero
            #"(lambda (i)
               (format t "Error is: ~a~%" (message i))
               (invoke-restart "return-zero)
            )
         )
         (div-zero-handle)
      )
   )
)
  
(handler-bind
   (
      (dividing-by-zero
         #"(lambda (i)
            (format t "Error is: ~a~%" (message i))
            (invoke-restart "return-val 0)
         )
      )
   )
   (div-zero-handle)
)
  
(handler-bind
   (
      (dividing-by-zero
         #"(lambda (i)
            (format t "Error is: ~a~%" (message i))
            (invoke-restart "recalculate-with 3)
         )
      )
   )
   (div-zero-handle)
)
  
(handler-bind
   (
      (dividing-by-zero
         #"(lambda (i)
            (format t "Error is: ~a~%" (message i))
            (invoke-restart "continue)
         )
      )
   )
   (div-zero-handle)
)
  
(format t "Finished executing all cases!"))

Выход:

Обработчик-случай VS Обработчик-привязка:

Формы try/catch, используемые в других языках, сравнимы с регистром-обработчиком. Когда нам нужен полный контроль над тем, что происходит, когда возникает сигнал, мы должны использовать привязку обработчика. Он перезапускается интерактивно или программно и позволяет нам использовать отладчик.

Тот факт, что функция-обработчик, связанная с помощью Handler-bind, будет вызываться без раскручивания стека, сохраняя управление в вызове parse-log-entry при вызове этой функции, является более существенным различием между Handler-bind и Handler-case. Самый последний связанный перезапуск с указанным именем будет найден и вызван командой invoke-restart. Мы можем наблюдать перезапуски (созданные перезапуском-кейсом) где угодно глубоко в стеке, в том числе перезапуски, установленные другими библиотеками, функции которых вызывала эта библиотека, если какая-то библиотека не улавливает все ситуации и позволяет некоторым выскочить к нам. И мы можем видеть трассировку стека, которая включает в себя каждый вызванный фрейм, а также локальные переменные и другие вещи в некоторых лиспах. Все раскручивается, как только мы забываем об этом после рассмотрения дела. Стек не перематывается обработчиком-привязкой.

Условия сигнализации (броска):

В дополнение к «системе условий» в общем LISP также доступен ряд функций, которые можно использовать для сигнализации об ошибках. Однако способ обработки ошибки после сообщения о ней зависит от реализации.

Сообщение об ошибке задается программой пользователя. Функции анализируют это сообщение и могут показывать его пользователю, а могут и не показывать. Поскольку система LISP будет обрабатывать их в соответствии со своим предпочтительным стилем, сообщения об ошибках не должны содержать символ новой строки ни в начале, ни в конце, или для обозначения ошибки. Вместо этого их следует создавать с помощью функции форматирования.

Ниже приведены несколько часто используемых подпрограмм для предупреждений, прерываний и фатальных и нефатальных ошибок:

error format-string &rest args

  • Это указывает на фатальную ошибку. Такие ошибки не могут быть устранены, поэтому они никогда не возвращаются к тому, кто их вызвал.

Мы можем использовать ошибку двумя способами:

  • (Ошибка «какой-то текст»): указывает на состояние простой ошибки.
  • (Сообщение об ошибке: «Мы попробовали то и это, но это не сработало».)

cerror continue-format-string error-format-string &rest args

  • Заходит в отладчик и выдает ошибку. Однако после устранения проблемы он позволяет продолжить программу из отладчика.
  • cerror возвращает nil, если программа возобновляется после возникновения ошибки. За вызовом cerror следует выполнение следующего кода. Этот код должен исправить проблему, возможно, запросив у пользователя новое значение, если переменная была неверной.
  • Аргумент continue-format-string предоставляется в качестве управляющей строки для форматирования вместе с аргументами для создания строки сообщения, подобно аргументу error-format-string.

warn format-string &rest args

  • Обычно он не входит в отладчик, а вместо этого выводит сообщение об ошибке.
  • Реализация предупреждения должна позаботиться о перемещении сообщения об ошибке на новую строку до и после него, а также, возможно, указать имя функции, вызвавшей предупреждение.

break &optional format-string &rest args

  • Без какой-либо возможности быть перехваченным запрограммированными инструментами обработки ошибок, он выводит сообщение и сразу же входит в отладчик.
  • Break возвращает ноль, если продолжается. При использовании break параметры не требуются, и будет доставлено подходящее сообщение по умолчанию.
  • Предполагается, что использование команды break для введения в программу временных «точек останова» отладки, а не для указания проблем, предотвратит любые непредвиденные действия по восстановлению. По этой причине Break не принимает дополнительный аргумент строки управления форматом, который принимает cerror.

Пример 3:

Lisp




; Lisp program to show signaling of 
; condition using error function.
; Program calculates square root of a number 
; and signals error if the number is negative.
  
(defun Square-root (i)
   (cond ((or (not (typep i "integer)) (minusp i))
      (error "~S is a negative number!" i))
      ((zerop i) 1)
      (t  (sqrt i))
   )
)
  
(write(Square-root 25))
(terpri)
(write(Square-root -3))

Выход:

Пользовательские сообщения об ошибках:

До сих пор всякий раз, когда возникает ошибка или сообщается об ошибке, мы видели текст по умолчанию в отладчике, который отображал тип условия. Это сообщение об ошибке по умолчанию в отладчике может быть настроено по желанию программиста с помощью функции :report .

Сообщение по умолчанию в отладчике:

Condition COMMON-LISP-USER::MY-DIVISION-BY-ZERO was signaled.
 [Condition of type MY-DIVISION-BY-ZERO]

мы можем написать функцию :report в объявлении условия, чтобы указать сообщение, которое будет отображаться в отладчике при возникновении условия.

Пример:

(define-condition my-division-by-zero (error)
 ((dividend :initarg :dividend
            :initform nil
            :accessor dividend))

 ;; the :report is the message into the debugger:
 (:report (lambda (condition stream)
    (format stream “You were going to divide ~a by zero.~&” (dividend condition)))))

Сообщение в отладчике:

 You were going to divide 3 by zero.
   [Condition of type MY-DIVISION-BY-ZERO]

Иерархия условий:

Иерархия различных условий изображена ниже:

Ниже приведен список приоритетов класса простых ошибок:

  • простая ошибка
  • простое условие
  • ошибка
  • тяжелое состояние
  • условие
  • т

Ниже приведен список приоритетов классов простых предупреждений:

  • простое предупреждение
  • простое условие
  • предупреждение
  • условие
  • т