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的基本形式如下 −
(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 |
error format-string &rest args 它表示致命错误。 出现这种错误是无法继续的; 因此错误永远不会返回给它的调用者。 |
2 |
cerror continue-format-string error-format-string &rest args 它发出错误信号并进入调试器。 但是,它允许在解决错误后从调试器继续程序。 |
3 |
warn format-string &rest args 它会打印一条错误消息,但通常不会进入调试器 |
4 |
break &optional format-string &rest args 它打印消息并直接进入调试器,不允许被编程的错误处理设施拦截 |
示例
在此示例中,阶乘函数计算数字的阶乘; 但是,如果参数为负,则会引发错误条件。
创建一个名为 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.