LISP - Tratamento de Erros

Na terminologia LISP comum, as exceções são chamadas de condições.

Na verdade, as condições são mais gerais do que exceções nas linguagens de programação tradicionais, porque um condition representa qualquer ocorrência, erro ou não, que pode afetar vários níveis da pilha de chamadas de função.

O mecanismo de tratamento de condições no LISP lida com essas situações de forma que as condições sejam usadas para sinalizar um aviso (digamos, imprimindo um aviso), enquanto o código de nível superior na pilha de chamadas pode continuar seu trabalho.

O sistema de tratamento de condições no LISP tem três partes -

  • Sinalizando uma condição
  • Lidando com a condição
  • Reinicie o processo

Lidando com uma condição

Tomemos um exemplo de tratamento de uma condição que surge da divisão por zero condição, para explicar os conceitos aqui.

Você precisa seguir os seguintes passos para lidar com uma condição -

  • Define the Condition - “Uma condição é um objeto cuja classe indica a natureza geral da condição e cujos dados de instância carregam informações sobre os detalhes das circunstâncias particulares que levaram à condição a ser sinalizada”.

    A macro define-condition é usada para definir uma condição, que tem a seguinte sintaxe -

(define-condition condition-name (error)
   ((text :initarg :text :reader text))
)
  • Novos objetos de condição são criados com a macro MAKE-CONDITION, que inicializa os slots da nova condição com base no :initargs argumento.

Em nosso exemplo, o código a seguir define a condição -

(define-condition on-division-by-zero (error)
   ((message :initarg :message :reader message))
)
  • Writing the Handlers- um manipulador de condição é um código usado para manipular a condição sinalizada nele. Geralmente é escrito em uma das funções de nível superior que chamam a função de erro. Quando uma condição é sinalizada, o mecanismo de sinalização procura um manipulador apropriado com base na classe da condição.

    Cada manipulador consiste em -

    • Especificador de tipo, que indica o tipo de condição que pode tratar
    • Uma função que leva um único argumento, a condição

    Quando uma condição é sinalizada, o mecanismo de sinalização encontra o manipulador estabelecido mais recentemente que é compatível com o tipo de condição e chama sua função.

    A macro handler-caseestabelece um manipulador de condição. A forma básica de um caso de manipulador -

(handler-case expression error-clause*)

Onde, cada cláusula de erro tem a forma -

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

    Este é o código que realmente recupera seu programa de erros, e os manipuladores de condição podem então tratar uma condição invocando uma reinicialização apropriada. O código de reinicialização geralmente é colocado em funções de nível médio ou baixo e os manipuladores de condição são colocados nos níveis superiores do aplicativo.

    o handler-bindmacro permite fornecer uma função de reinicialização e permite que você continue nas funções de nível inferior sem desfazer a pilha de chamadas de função. Em outras palavras, o fluxo de controle ainda estará na função de nível inferior.

    A forma básica de handler-bind é o seguinte -

(handler-bind (binding*) form*)

Onde cada ligação é uma lista do seguinte -

  • um tipo de condição
  • uma função manipuladora de um argumento

o invoke-restart macro encontra e chama a função de reinicialização associada mais recentemente com o nome especificado como argumento.

Você pode ter várias reinicializações.

Exemplo

Neste exemplo, demonstramos os conceitos acima escrevendo uma função chamada função-divisão, que criará uma condição de erro se o argumento divisor for zero. Temos três funções anônimas que fornecem três maneiras de sair disso - retornando um valor 1, enviando um divisor 2 e recalculando ou retornando 1.

Crie um novo arquivo de código-fonte denominado main.lisp e digite o seguinte código nele.

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

Quando você executa o código, ele retorna o seguinte resultado -

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

Além do 'Sistema de condição', conforme discutido acima, o Common LISP também fornece várias funções que podem ser chamadas para sinalizar um erro. O tratamento de um erro, quando sinalizado, é, no entanto, dependente da implementação.

Funções de sinalização de erro no LISP

A tabela a seguir fornece funções comumente usadas sinalizando avisos, interrupções, erros não fatais e fatais.

O programa do usuário especifica uma mensagem de erro (uma string). As funções processam esta mensagem e podem / não exibi-la para o usuário.

As mensagens de erro devem ser construídas aplicando o format função, não deve conter um caractere de nova linha no início ou no final e não precisa indicar erro, pois o sistema LISP cuidará deles de acordo com seu estilo preferido.

Sr. Não. Função e descrição
1

error format-string & rest args

Sinaliza um erro fatal. É impossível continuar com esse tipo de erro; portanto, o erro nunca retornará ao chamador.

2

cerror continue-format-string error-format-string e argumentos restantes

Ele sinaliza um erro e entra no depurador. No entanto, permite que o programa continue a partir do depurador após a resolução do erro.

3

warn format-string & rest args

ele imprime uma mensagem de erro, mas normalmente não vai para o depurador

4

break& string de formato opcional e args restantes

Ele imprime a mensagem e vai diretamente para o depurador, sem permitir qualquer possibilidade de interceptação por recursos de tratamento de erros programados

Exemplo

Neste exemplo, a função fatorial calcula o fatorial de um número; entretanto, se o argumento for negativo, ele gerará uma condição de erro.

Crie um novo arquivo de código-fonte denominado main.lisp e digite o seguinte código nele.

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

Quando você executa o código, ele retorna o seguinte resultado -

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