14.6 状况 (Conditions)

在 Common Lisp 里,状况 (condition)包括了错误以及其它可能在执行期发生的情况。当一个状况被捕捉时 (signalled),相应的处理程序 (handler)会被调用。处理错误状况的缺省处理程序通常会调用一个中断循环 (break-loop)。但 Common Lisp 提供了多样的操作符来捕捉及处理错误。要覆写缺省的处理程序,甚至是自己写一个新的处理程序也是有可能的。

多数的程序员不会直接处理状况。然而有许多更抽象的操作符使用了状况,而要了解这些操作符,知道背后的原理是很有用的。

Common lisp 有数个操作符用来捕捉错误。最基本的是 error 。一个调用它的方法是给入你会给 format 的相同参数:

  1. > (error "Your report uses ~A as a verb." 'status)
  2. Error: Your report uses STATUS as a verb
  3. Options: :abort, :backtrace
  4. >>

如上所示,除非这样的状况被处理好了,不然执行就会被打断。

用来捕捉错误的更抽象操作符包括了 ecasecheck-type 以及 assert 。前者与 case 相似,要是没有键值匹配时会捕捉一个错误:

  1. > (ecase 1 (2 3) (4 5))
  2. Error: No applicable clause
  3. Options: :abort, :backtrace
  4. >>

普通的 case 在没有键值匹配时会返回 nil ,但由于利用这个返回值是很差的编码风格,你或许会在当你没有 otherwise 子句时使用 ecase

check-type 宏接受一个位置,一个类型名以及一个选择性字符串,并在该位置的值不是预期的类型时,捕捉一个可修正的错误 (correctable error)。一个可修正错误的处理程序会给我们一个机会来提供一个新的值:

  1. > (let ((x '(a b c)))
  2. (check-type (car x) integer "an integer")
  3. x)
  4. Error: The value of (CAR X), A, should be an integer.
  5. Options: :abort, :backtrace, :continue
  6. >> :continue
  7. New value of (CAR X)? 99
  8. (99 B C)
  9. >

在这个例子里, (car x) 被设为我们提供的新值,并重新执行,返回了要是 (car x) 本来就包含我们所提供的值所会返回的结果。

这个宏是用更通用的 assert 所定义的, assert 接受一个测试表达式以及一个有着一个或多个位置的列表,伴随着你可能传给 error 的参数:

  1. > (let ((sandwich '(ham on rye)))
  2. (assert (eql (car sandwich) 'chicken)
  3. ((car sandwich))
  4. "I wanted a ~A sandwich." 'chicken)
  5. sandwich)
  6. Error: I wanted a CHICKEN sandwich.
  7. Options: :abort, :backtrace, :continue
  8. >> :continue
  9. New value of (CAR SANDWICH)? 'chicken
  10. (CHICKEN ON RYE)

要建立新的处理程序也是可能的,但大多数程序员只会间接的利用这个可能性,通过使用像是 ignore-errors 的宏。如果它的参数没产生错误时像在 progn 里求值一样,但要是在求值过程中,不管什么参数报错,执行是不会被打断的。取而代之的是, ignore-errors 表达式会直接返回两个值: nil 以及捕捉到的状况。

举例来说,如果在某个时候,你想要用户能够输入一个表达式,但你不想要在输入是语法上不合时中断执行,你可以这样写:

  1. (defun user-input (prompt)
  2. (format t prompt)
  3. (let ((str (read-line)))
  4. (or (ignore-errors (read-from-string str))
  5. nil)))

若输入包含语法错误时,这个函数仅返回 nil :

  1. > (user-input "Please type an expression")
  2. Please type an expression> #%@#+!!
  3. NIL

脚注

[1]虽然标准没有提到这件事,你可以假定 and 以及 or 类型标示符仅考虑它们所要考虑的参数,与 orand 宏类似。
[2]某些 Common Lisp 实现,当我们不在用户包下时,会在顶层提示符前打印包的名字。