Manejo de errores en LISP

El sistema de condiciones en Lisp es una de sus mejores características. Realiza la misma tarea que los mecanismos de manejo de excepciones en Java, Python y C++, pero es más adaptable. En realidad, su adaptabilidad va más allá del manejo de errores ya que las condiciones, que son más flexibles que las excepciones, pueden representar cualquier evento que ocurra mientras se ejecuta un programa y puede ser de interés para el código en varios niveles de la pila de llamadas. El sistema de condiciones es más flexible que los sistemas de excepción porque divide las tareas en tres partes: señalar una condición, manejarla y reiniciar. Los sistemas de excepción dividen las tareas en dos partes: el código que maneja el problema y el código que señala la condición.

Una condición es un objeto cuya clase describe sus características generales y cuyos datos de instancia contienen información sobre los detalles de los eventos que llevaron a que se indique la condición. Con la excepción del hecho de que la superclase predeterminada de clases definidas con DEFINE-CONDITION es CONDITION en lugar de STANDARD-OBJECT, las clases de condición se definen mediante la macro DEFINE-CONDITION.

Para definir una condición:

Sintaxis:

(definir-condición nombre-condición (error)
  ((text :initarg :text :reader text))
)

Atrapar cualquier condición (manejador-caso):

Un código utilizado para manejar la situación señalada allí se conoce como manejador de condiciones. La función de error normalmente se llama desde una de las funciones de nivel superior donde normalmente se define. El mecanismo de señalización busca un controlador adecuado cuando se notifica una condición en función de la clase de la condición. Cada controlador tiene un especificador de tipo que indica las situaciones que puede manejar, así como una función que solo acepta la condición como su único argumento.

El mecanismo de señalización localiza el controlador más reciente que es compatible con el tipo de condición y llama a su función cada vez que se señala una condición. La macro Handler-case crea un manejador de condiciones.

Sintaxis:

(handler-case (código que da error)
  (condición-tipo (la-condición)       

 ;; <– argumento opcional
     (código))                                          

;;<– código de cláusula de error
  ( otra-condición (la-condición)
    …))

El Handler-case devuelve su valor si el código que encuentra un error regresa normalmente. Un caso de Handler-body debe consistir en una sola expresión; PROGN se puede usar para agregar muchas expresiones en una sola forma. El código en la cláusula de error relevante se ejecuta y su valor es devuelto por el caso del controlador, si el código que no se ejecuta señala una condición que es una instancia de cualquiera de los tipos de condición enumerados en cualquier cláusula de error.

Captura de una condición específica:

Al escribir el tipo de condición, podemos decirles a los manejadores de condiciones qué condición manejar. Este proceso es comparable a un intento/captura que se encuentra en otros lenguajes de programación, pero podemos lograr más.

Ejemplo 1:

Lisp

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

Producción:

 

Frecuentemente encontrará la siguiente advertencia del compilador si mantiene el objeto de condición como un argumento pero no accede a él en sus controladores:

; capturado STYLE-WARNING:
; La variable C está definida pero nunca se usa.

Use una llamada de declaración como se muestra a continuación para eliminarla:

(handler-case (/ 3 0)
 (división por cero (c)
 (declarar (ignorar c))

(formato t “División capturada por cero~%”)))

;; no imprimimos “c” aquí y no recibimos la advertencia.

Condiciones de manejo (Handler-bind):

Los controladores de condiciones pueden responder a una condición activando el reinicio adecuado desde la Fase de reinicio, que es el código que realmente recupera su aplicación de los problemas. El código de reinicio generalmente se inserta en funciones de bajo o medio nivel, mientras que los controladores de condiciones se ubican en los niveles superiores de la aplicación. Al usar la macro Handler-bind, puede continuar con funciones de nivel inferior sin tener que deshacer la pila de llamadas de función y dar una función de reinicio. En otras palabras, la función de nivel inferior seguirá teniendo control sobre el proceso. La sintaxis de enlace de controlador es la siguiente:

(handler-bind ((una-condición #’función-para-manejarlo)         

;;<– vinculante
 (otro-uno #’otra-función))
 (código que puede…)
 (…salida de error))

Cada enlace tiene una función de controlador con un argumento y un tipo de condición. La macro de invocación-reinicio busca y llama a la función de reinicio más reciente que se ha enlazado, pasando el nombre proporcionado como argumento.

Ejemplo 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!"))

Producción:

 

Manejador-caso VS Manejador-bind:

Los formularios try/catch que se usan en otros idiomas son comparables a handler-case. Cuando necesitamos un control completo sobre lo que sucede cuando se genera una señal, debemos utilizar un enlace de controlador. Se reinicia de forma interactiva o programática y nos permite usar el depurador.

El hecho de que la función de controlador vinculada por Handler-bind se invoque sin desenredar la pila, manteniendo el control en la llamada a parse-log-entry cuando se llama a esta función, es una distinción más significativa entre Handler-bind y Handler-case. El reinicio enlazado más reciente con el nombre especificado será encontrado e invocado por el comando de reinicio de invocación. Podemos observar reinicios (creados por restart-case) en cualquier lugar profundo de la pila, incluidos los reinicios establecidos por otras bibliotecas cuyas funciones llamó esta biblioteca, si alguna biblioteca no detecta todas las situaciones y permite que nos salgan algunas burbujas. Y podemos ver el seguimiento de la pila, que incluye todos los fotogramas que se llamaron, así como las variables locales y otras cosas en algunos balbuceos. Todo se deshace una vez que nos olvidamos de esto después de manejar un caso. El handler-bind no rebobina la pila.

Condiciones de señalización (lanzamiento):

Además del «Sistema de condiciones», también hay disponibles varias funciones en LISP común que se pueden usar para señalar errores. Sin embargo, la forma en que se maneja un error una vez que se ha informado depende de la implementación.

El programa de usuario especifica un mensaje de error. Las funciones analizan este mensaje y pueden o no mostrarlo al usuario. Dado que el sistema LISP los manejará de acuerdo con su estilo preferido, los mensajes de error no necesitan contener un carácter de nueva línea al principio o al final o indicar un error. En su lugar, deben crearse utilizando la función de formato.

Las siguientes son algunas rutinas de uso frecuente para advertencias, pausas y errores fatales y no fatales:

string de formato de error &resto de argumentos

  • Indica un error fatal. Dichos errores no se pueden recuperar, por lo que nunca regresan a la persona que los llamó.

Podemos usar el error de dos maneras:

  • (Error «algún texto»): indica una condición de error simple
  • (Mensaje de error: «Probamos esto y aquello, pero no funcionó»).

error string-de-formato-continuación string-de-formato-error &resto de argumentos

  • Entra en el depurador y genera un error. Sin embargo, después de solucionar el problema, permite la continuación del programa desde el depurador.
  • error devuelve nil si el programa se reanuda después de encontrarse con un error. La llamada al error es seguida posteriormente por la ejecución del siguiente código. Este código debería solucionar el problema, tal vez pidiendo al usuario un nuevo valor si una variable era incorrecta.
  • El argumento continue-format-string se proporciona como una string de control para formatear junto con los argumentos para crear una string de mensaje, de forma similar a como lo es el argumento error-format-string.

advertir format-string &rest args

  • Por lo general, no ingresa al depurador, sino que imprime un mensaje de error.
  • Una implementación de advertencia debe encargarse de mover el mensaje de error a una nueva línea antes y después, así como proporcionar el nombre de la función que llamó a advertir.

romper &formato-string opcional &restar argumentos

  • Sin ninguna posibilidad de ser interceptado por herramientas programadas de manejo de errores, emite el mensaje e ingresa al depurador de inmediato.
  • Break devuelve cero si continúa. No se requieren parámetros cuando se usa break, y se entregará un mensaje predeterminado adecuado.
  • Se supone que el uso del comando break para inyectar «puntos de interrupción» de depuración temporales en un programa en lugar de indicar problemas evitará que se produzcan acciones de recuperación inesperadas. Break no acepta el argumento de string de control de formato adicional que acepta error, por este motivo.

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

Producción:

 

Mensajes de error personalizados:

Hasta ahora, cada vez que se lanza o señala un error, vimos un texto predeterminado en el depurador que mostraba el tipo de condición. Este mensaje de error predeterminado en el depurador se puede personalizar de acuerdo con el deseo del programador con la ayuda de la función :report .

Mensaje predeterminado en el depurador:

Se señaló la condición COMMON-LISP-USER::MY-DIVISION-BY-ZERO.
 [Condición de tipo MY-DIVISION-BY-ZERO]

podemos escribir la función :report en la declaración de la condición para especificar el mensaje que se mostrará en el depurador cuando se lance la condición.

Ejemplo:

(definir-condición mi-división-por-cero (error)
 ((dividend :initarg :dividend
            :initform nil
            :accessor dividend))

 ;; el :informe es el mensaje en el depurador:
 ( :report (lambda (flujo de condición)
    (flujo de formato “Ibas a dividir ~a por cero.~&” (condición de dividendo)))))

Mensaje en el depurador:

 Ibas a dividir 3 por cero.
   [Condición de tipo MY-DIVISION-BY-ZERO]

Jerarquía de condiciones:

La jerarquía de las diferentes condiciones se muestra a continuación:

La siguiente es la lista de precedencia de clase de error simple:

  • error simple
  • condición simple
  • error
  • condición seria
  • condición

La siguiente es la lista de precedencia de clase de advertencia simple:

  • advertencia simple
  • condición simple
  • advertencia
  • condición
  • t

Publicación traducida automáticamente

Artículo escrito por himanshu77deer y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *