- LISP 教程
- LISP - 首页
- LISP - 概述
- LISP - 环境
- LISP - 程序结构
- LISP - 基本语法
- LISP - 数据类型
- LISP - 宏
- LISP - 变量
- LISP - 常量
- LISP - 运算符
- LISP - 决策
- LISP - 循环
- LISP - 函数
- LISP - 谓词
- LISP - 数字
- LISP - 字符
- LISP - 数组
- LISP - 字符串
- LISP - 序列
- LISP - 列表
- LISP - 符号
- LISP - 向量
- LISP - 集合
- LISP - 树
- LISP - 哈希表
- LISP - 输入与输出
- LISP - 文件I/O
- LISP - 结构体
- LISP - 包
- LISP - 错误处理
- LISP - CLOS
- LISP 有用资源
- Lisp - 快速指南
- Lisp - 有用资源
- Lisp - 讨论
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的基本形式:
(handler-case expression error-clause*)
其中,每个错误子句都具有以下形式:
condition-type ([var]) code)
重启阶段
这是实际从错误中恢复程序的代码,然后条件处理程序可以通过调用适当的重启来处理条件。重启代码通常放置在中级或低级函数中,而条件处理程序则放置在应用程序的上层。
handler-bind宏允许您提供重启函数,并允许您在不展开函数调用栈的情况下继续执行低级函数。换句话说,控制流仍然在低级函数中。
handler-bind的基本形式如下:
(handler-bind (binding*) form*)
其中每个绑定都是以下列表:
- 条件类型
- 一个带有单个参数的处理程序函数
invoke-restart宏查找并调用最近绑定的重启函数,并使用指定的名称作为参数。
您可以有多个重启。
示例
在此示例中,我们通过编写一个名为 division-function 的函数来演示上述概念,如果除数参数为零,该函数将创建错误条件。我们有三个匿名函数,它们提供了三种退出方法:返回一个值 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 它发出致命错误。无法从这种错误中继续;因此,error 永远不会返回到其调用者。 |
2 |
cerror continue-format-string error-format-string &rest args 它发出错误并进入调试器。但是,它允许程序在解决错误后从调试器中继续执行。 |
3 |
warn format-string &rest args 它打印错误消息,但通常不会进入调试器 |
4 |
break &optional format-string &rest args 它打印消息并直接进入调试器,不允许程序错误处理功能拦截。 |
示例
在此示例中,factorial 函数计算数字的阶乘;但是,如果参数为负数,则会引发错误条件。
创建一个名为 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.