常见的 lisp 异常处理(条件和重启)

common lisp exception handling (condition and restart)

我已经阅读了 common lisp "Practical Common Lisp" 异常处理章节好几天了,但我现在对示例和解释感到困惑,同时我试图写一些测试示例,但没有成功'如我所料,下面是我的测试样本。

  1. 条件定义

    (define-condition evenp-error (error) 
      ((text :initarg :text :reader text)))
    
  2. 定义打印奇数的函数

    (defun filter-evenp (lst)
      (dolist (x lst)
        (if (not (evenp x)) 
          (print x)
          (error 'evenp-error :text x))))
    
  3. 重启函数

    (defun skip-evenp (c) (invoke-start 'skip-evenp))
    
  4. 重启案例

    (restart-case (filter-evenp (list 1 2 3 4 5))
      (skip-evenp () nil))
    

我只想打印所有奇数并跳过偶数错误,我的样本有什么问题?有人帮忙吗?非常感谢!!

您需要将 RESTART-CASE 放到您想要重新开始执行的位置:

(defun filter-evenp (lst)
  (dolist (x lst)
    (restart-case
        (if (not (evenp x))
            (print x)
            (error 'evenp-error :text x))
      (skip-evenp () nil))))

那么你应该使用HANDLER-BIND来处理错误:

(handler-bind ((evenp-error #'skip-evenp))
  (filter-evenp (list 1 2 3 4 5)))

Practical Common Lisp is quite detailed but it is true that the condition system might require some time to get used to. You might be interested by Kent Pitman's articles: Exceptional Situations in Lisp and Condition Handling in the Lisp Language Family.

What's a condition system and why do you want one?. There are also many other references lilke this Wikibooks entry or C2 wiki's CommonLispConditionSystem 条目中引用了这些内容。

定义重启

A RESTART-CASE 基本上说:

I am going to execute this form and I don't care if it signals a condition or not. But if it does and you want to recover from that situation, here are different ways I can work around the problem (retry, ignore, etc.).

您通常无法说明如何从调用点调用的代码中的错误中恢复。换句话说,filter-evenp 应该用 restart-case 包装代码以提供替代路径。对于您的示例,使用 CERROR 就足够了,它会在建立 CONTINUE 重新启动时发出错误信号。

(if (evenp x)
  (cerror "Ignore even number" 'evenp-error :text x) 
  (print x))

作为练习,您可以尝试用明确的 restart-case 构造替换 (cerror ...)

然后,如果您测试代码,您应该会看到调试器弹出并显示 CONTINUE 重新启动。如果您定义了自己的重启,则可以使用不同的名称。

调用重启

在您的 skip-evenp 函数中,您正在调用此时尚未建立的重启,我认为您对 skip-evenp 同时命名重启和函数感到困惑。

你应该做的是通过调用重启来处理错误。

在这里,您希望发出错误信号的代码继续,所以您真的不想展开执行堆栈。这就是为什么你必须使用 HANDLER-BIND.

(handler-bind ((evenp-error (lambda (e) (invoke-restart 'continue))))
  (filter-evenp '(1 2 3 4)))
1
3    

您当然可以像您一样将匿名 lambda 提取到自定义函数中。