LISP - Manejo de errores

En la terminología común de LISP, las excepciones se denominan condiciones.

De hecho, las condiciones son más generales que las excepciones en los lenguajes de programación tradicionales, porque un condition representa cualquier ocurrencia, error o no, que podría afectar varios niveles de pila de llamadas de función.

El mecanismo de manejo de condiciones en LISP maneja tales situaciones de tal manera que las condiciones se utilizan para señalar una advertencia (por ejemplo, imprimiendo una advertencia) mientras que el código de nivel superior en la pila de llamadas puede continuar su trabajo.

El sistema de manejo de condiciones en LISP tiene tres partes:

  • Señalando una condición
  • Manejando la condición
  • Reiniciar el proceso

Manejo de una condición

Tomemos un ejemplo del manejo de una condición que surge de la condición de dividir por cero, para explicar los conceptos aquí.

Debe seguir los siguientes pasos para manejar una condición:

  • Define the Condition - "Una condición es un objeto cuya clase indica la naturaleza general de la condición y cuyos datos de instancia contienen información sobre los detalles de las circunstancias particulares que conducen a que la condición sea señalada".

    La macro define-condition se utiliza para definir una condición, que tiene la siguiente sintaxis:

(define-condition condition-name (error)
   ((text :initarg :text :reader text))
)
  • Los nuevos objetos de condición se crean con la macro MAKE-CONDITION, que inicializa las ranuras de la nueva condición en función de la :initargs argumento.

En nuestro ejemplo, el siguiente código define la condición:

(define-condition on-division-by-zero (error)
   ((message :initarg :message :reader message))
)
  • Writing the Handlers- un manejador de condiciones es un código que se utiliza para manejar la condición señalada en el mismo. Generalmente está escrito en una de las funciones de nivel superior que llaman a la función de error. Cuando se señala una condición, el mecanismo de señalización busca un manejador apropiado en función de la clase de la condición.

    Cada controlador consta de:

    • Especificador de tipo, que indica el tipo de condición que puede manejar
    • Una función que toma un solo argumento, la condición

    Cuando se señaliza una condición, el mecanismo de señalización encuentra el manejador establecido más recientemente que sea compatible con el tipo de condición y llama a su función.

    La macro handler-caseestablece un manejador de condiciones. La forma básica de un caso de manejo -

(handler-case expression error-clause*)

Donde, cada cláusula de error tiene la forma -

condition-type ([var]) code)
  • Restarting Phase

    Este es el código que realmente recupera su programa de errores, y los controladores de condiciones pueden manejar una condición invocando un reinicio apropiado. El código de reinicio generalmente se coloca en funciones de nivel medio o bajo y los controladores de condición se ubican en los niveles superiores de la aplicación.

    los handler-bindmacro le permite proporcionar una función de reinicio y le permite continuar en las funciones de nivel inferior sin deshacer la pila de llamadas de función. En otras palabras, el flujo de control seguirá estando en la función de nivel inferior.

    La forma básica de handler-bind es como sigue -

(handler-bind (binding*) form*)

Donde cada enlace es una lista de lo siguiente:

  • un tipo de condición
  • una función de controlador de un argumento

los invoke-restart macro busca e invoca la función de reinicio enlazada más recientemente con el nombre especificado como argumento.

Puede tener varios reinicios.

Ejemplo

En este ejemplo, demostramos los conceptos anteriores escribiendo una función llamada función de división, que creará una condición de error si el argumento del divisor es cero. Tenemos tres funciones anónimas que brindan tres formas de salir de él: devolviendo un valor 1, enviando un divisor 2 y recalculando, o devolviendo 1.

Cree un nuevo archivo de código fuente llamado main.lisp y escriba el siguiente código en él.

(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."))

Cuando ejecuta el código, devuelve el siguiente resultado:

error signaled: denominator is zero
Value: 1
error signaled: denominator is zero
Value: 5
error signaled: denominator is zero
Done.

Aparte del 'Sistema de condiciones', como se discutió anteriormente, Common LISP también proporciona varias funciones que pueden ser solicitadas para señalar un error. Sin embargo, el manejo de un error, cuando se señala, depende de la implementación.

Funciones de señalización de errores en LISP

La siguiente tabla proporciona funciones de uso común que indican advertencias, interrupciones, errores fatales y no fatales.

El programa de usuario especifica un mensaje de error (una cadena). Las funciones procesan este mensaje y pueden o no mostrarlo al usuario.

Los mensajes de error deben construirse aplicando el format función, no debe contener un carácter de nueva línea ni al principio ni al final, y no es necesario que indique un error, ya que el sistema LISP se encargará de estos de acuerdo con su estilo preferido.

No Señor. Función y descripción
1

error formato-cadena y argumentos de descanso

Señala un error fatal. Es imposible continuar con este tipo de error; por lo tanto, el error nunca volverá a su llamador.

2

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

Señala un error y entra en el depurador. Sin embargo, permite que el programa continúe desde el depurador después de resolver el error.

3

warn formato-cadena y argumentos de descanso

imprime un mensaje de error pero normalmente no entra en el depurador

4

breaky opcionales formato de cuerdas y de descanso args

Imprime el mensaje y va directamente al depurador, sin permitir ninguna posibilidad de interceptación por parte de las instalaciones de manejo de errores programadas.

Ejemplo

En este ejemplo, la función factorial calcula el factorial de un número; sin embargo, si el argumento es negativo, genera una condición de error.

Cree un nuevo archivo de código fuente llamado main.lisp y escriba el siguiente código en él.

(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))

Cuando ejecuta el código, devuelve el siguiente resultado:

120
*** - -1 is a negative number.