Common Lisp 异常处理(条件和重启)

7

我已经阅读了几天Common Lisp中的异常处理章节《Practical Common Lisp》,但是我现在对示例和解释感到非常困惑。同时,我尝试编写一些测试用例,但是它并没有按照我的预期工作,以下是我的测试用例。

  1. Condition definition

    (define-condition evenp-error (error) 
      ((text :initarg :text :reader text)))
    
  2. Define function that prints odd number

    (defun filter-evenp (lst)
      (dolist (x lst)
        (if (not (evenp x)) 
          (print x)
          (error 'evenp-error :text x))))
    
  3. Restart function

    (defun skip-evenp (c) (invoke-start 'skip-evenp))
    
  4. Restart case

    (restart-case (filter-evenp (list 1 2 3 4 5))
      (skip-evenp () nil))
    
我想做的是打印所有奇数并跳过偶数错误,我的示例有什么问题?有人可以帮忙吗?非常感谢!!
2个回答

7

实用的通用Lisp非常详细,但是条件系统可能需要一些时间才能习惯。您可能会对肯特·皮特曼(Kent Pitman)的文章感兴趣:Lisp中的异常情况Lisp语言系列中的条件处理

这些都在什么是条件系统,为什么您需要一个?中引用。还有许多其他类似于Wikibooks条目或C2 wiki的CommonLispConditionSystem条目的参考资料。

定义重启

RESTART-CASE 基本上是这样说的:

我将执行此表单,无论它是否发出信号。但如果它确实发出信号,并且您想从该情况中恢复,那么我可以通过不同的方式解决问题(重试、忽略等)。

通常情况下,您不能确定在调用点调用的代码中如何从错误中恢复。换句话说,应该由 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 提取到自定义函数中。

你提供的解决方案正是我想要的,但我仍然对重启的事情感到困惑,让我试着找一些更多的资料来弄清楚,谢谢! - user2015063
@user2015063 如果这个用户对您有帮助,请考虑使用绿色勾号接受它。如果需要,我可以添加更多参考资料来帮助您。 - coredump

6

您需要将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)))

谢谢您的回复。我还不确定#'skip-evenp是指什么地方? - user2015063
#'SKIP-EVENP 是指名为该名称的函数(这就是 #' 的含义)。HANDLER-BIND 告诉它通过调用函数 SKIP-EVENP 来处理任何 EVENP-ERROR - jkiiski
1
@user2015063 #' 是一个读取宏:上述代码等同于 (function skip-event),并使用 function 特殊运算符来引用词法绑定到 skip-event 符号的函数对象。 - coredump
@jkiiski 非常感谢,我在谷歌上搜索了更多的示例后终于弄明白了。 - user2015063

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接