错误处理(Error Handling)
在通用LISP术语中,例外称为条件。
事实上,条件比传统编程语言中的异常更通用,因为condition表示任何可能影响各种级别的函数调用堆栈的事件,错误或不存在。
LISP中的条件处理机制以这样的方式处理这种情况,即条件用于发出警告(例如通过打印警告),同时调用堆栈上的上层代码可以继续其工作。
LISP中的状态处理系统有三个部分 -
- 发出信号
- 处理条件
- 重启过程
处理条件
让我们举一个处理由零除条件引起的条件的例子,来解释这里的概念。
您需要采取以下步骤来处理条件 -
Define the Condition - “条件是一个对象,其类指示条件的一般性质,其实例数据包含有关导致条件发出信号的特定情况的详细信息”。
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))
)
Writing the Handlers - 条件处理程序是用于处理在其上发出信号的条件的代码。 它通常写在一个调用错误函数的高级函数中。 当发出条件信号时,信令机制根据条件的类搜索适当的处理程序。
每个处理程序包括 -
- 类型说明符,表示它可以处理的条件类型
- 一个带有单个参数的函数,即条件
当发出条件信号时,信令机制找到与条件类型兼容的最近建立的处理程序并调用其函数。
宏handler-case建立一个条件处理程序。 处理程序案例的基本形式 -
(handler-case expression error-clause*)
其中,每个错误条款的形式 -
condition-type ([var]) code)
Restarting Phase
这是实际从错误中恢复程序的代码,然后条件处理程序可以通过调用适当的重新启动来处理条件。 重启代码通常放在中级或低级函数中,条件处理程序放在应用程序的上层。
handler-bind宏允许您提供重启功能,并允许您继续执行较低级别的功能而无需展开函数调用堆栈。 换句话说,控制流程仍将处于较低级别的功能。
handler-bind的基本形式如下 -
(handler-bind (binding*) form*)
每个绑定都是以下列表 -
- 条件类型
- 一个参数的处理函数
invoke-restart宏以指定的名称作为参数查找并调用最近绑定的重启函数。
您可以进行多次重启。
例子 (Example)
在这个例子中,我们通过编写一个名为division-function的函数来演示上述概念,如果divisor参数为零,它将创建一个错误条件。 我们有三个匿名函数提供了三种方法 - 通过返回值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系统将根据其首选样式处理这些错误消息。
Sr.No. | 功能和描述 |
---|---|
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 它打印消息并直接进入调试器,不允许任何可能被编程的错误处理设施拦截 |
例子 (Example)
在这个例子中,阶乘函数计算一个数的阶乘; 但是,如果参数为负数,则会引发错误条件。
创建一个名为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.