LISP - 错误处理


在 Common LISP 术语中,异常称为条件。

事实上,条件比传统编程语言中的异常更通用,因为条件代表任何发生、错误或不发生,这可能会影响函数调用堆栈的各个级别。

LISP 中的条件处理机制以这样的方式处理此类情况:条件用于发出警告信号(例如通过打印警告),而调用堆栈上的上层代码可以继续其工作。

LISP 中的条件处理系统由三个部分组成 -

  • 发出条件信号
  • 处理情况
  • 重新启动进程

处理条件

让我们以处理被零除条件引起的条件为例来解释这里的概念。

您需要采取以下步骤来处理情况 -

  • 定义条件- “条件是一个对象,其类指示条件的一般性质,其实例数据携带有关导致发出信号的特定情况的详细信息”。

    Define-condition 宏用于定义条件,其语法如下 -

(define-condition condition-name (error)
   ((text :initarg :text :reader text))
)
  • 新条件对象是使用 MAKE-CONDITION 宏创建的,该宏根据:initargs参数初始化新条件的槽。

在我们的示例中,以下代码定义了条件 -

(define-condition on-division-by-zero (error)
   ((message :initarg :message :reader message))
)
  • 编写处理程序- 条件处理程序是用于处理其上发出的条件信号的代码。它通常写在调用错误函数的高级函数之一中。当发出条件信号时,信号机制会根据条件的类别搜索适当的处理程序。

    每个处理程序包括 -

    • 类型说明符,指示它可以处理的条件类型
    • 接受单个参数(条件)的函数

    当发出条件信号时,信号机制会找到与该条件类型兼容的最近建立的处理程序并调用其函数。

    handler-case建立一个条件处理程序。处理程序案例的基本形式 -

(handler-case expression error-clause*)

其中,每个错误子句的形式为 -

condition-type ([var]) code)
  • 重启阶段

    这是实际从错误中恢复程序的代码,然后条件处理程序可以通过调用适当的重新启动来处理条件。重启代码通常放置在中层或低层函数中,而条件处理程序放置在应用程序的上层中。

    handler -bind宏允许您提供重新启动函数,并允许您继续执行较低级别的函数,而无需展开函数调用堆栈。换句话说,控制流程仍将在较低级别的函数中。

    处理程序绑定的基本形式如下 -

(handler-bind (binding*) form*)

其中每个绑定是以下列表 -

  • 条件类型
  • 一个参数的处理函数

invoke -restart宏查找并调用最近绑定的重新启动函数,并将指定名称作为参数。

您可以多次重新启动。

例子

在此示例中,我们通过编写一个名为 除法函数 的函数来演示上述概念,如果除数参数为零,该函数将创建一个错误条件。我们有三个匿名函数,它们提供了三种得出结果的方法 - 返回值 1、发送除数 2 并重新计算、或者返回 1。

创建一个名为 main.lisp 的新源代码文件,并在其中键入以下代码。

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

当您执行代码时,它会返回以下结果 -

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

除了上面讨论的“条件系统”之外,Common LISP 还提供了可以调用来发出错误信号的各种函数。然而,当发出信号时,对错误的处理取决于实现。

LISP 中的错误信号函数

下表提供了发出警告、中断、非致命和致命错误信号的常用函数。

用户程序指定错误消息(字符串)。这些函数处理此消息,并且可能/可能不将其显示给用户。

错误消息应该通过应用format函数来构造,不应在开头或结尾包含换行符,并且不需要指示错误,因为 LISP 系统将根据其首选样式来处理这些错误消息。

先生。 功能及说明
1

错误 格式字符串&rest args

它表示致命错误。出现这种错误是无法继续的;因此错误永远不会返回给它的调用者。

2

cerror 继续格式字符串 错误格式字符串&rest args

它发出错误信号并进入调试器。但是,它允许在解决错误后从调试器继续程序。

3

警告 格式字符串&rest args

它会打印一条错误消息,但通常不会进入调试器

4

中断&可选格式字符串&其余参数

它打印消息并直接进入调试器,不允许任何被编程的错误处理设施拦截的可能性

例子

在此示例中,阶乘函数计算数字的阶乘;但是,如果参数为负,则会引发错误情况。

创建一个名为 main.lisp 的新源代码文件,并在其中键入以下代码。

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

当您执行代码时,它会返回以下结果 -

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