处理程序"decline"处理信号究竟意味着什么?



HANDLER-BIND的HyperSpec条目中,它表示处理程序可以拒绝来处理信号。

然而,拒绝处理信号的链接词汇表条目并不是很有启发性:

拒绝v.(处理程序的)在没有处理正在发出信号的条件的情况下正常返回,允许信号处理过程继续,就像处理程序不存在一样。

此定义引出并没有回答什么构成正常返回的问题?

是否有构成";处理";信号

根据实践经验,我知道INVOKE-RESTART似乎符合这一标准。但这是处理程序";手柄;一个信号,还是还有其他信号?

我认为要理解"handle"的真正含义,你应该考虑条件系统是如何在引擎盖下工作的。Kent-Pitman在标准化过程中编写的示例实现是一个很好的起点(尽管它有一些笨拙的东西,比如基本上实现整个对象系统,因为CLOS还不是语言的一部分)。

粗略地说,handler-bind的作用是设置一个特殊的变量,我们将其称为*handler-clusters*,以便它保存(type . function)对的列表列表,与绑定列表相对应。一个可能的定义是

(defmacro handler-bind (bindings &body forms)
`(let ((*handler-clusters* (cons (list (mapcar #'(lambda (x) `(cons ',(car x) ,(cadr x)))
bindings))
*handler-clusters*)))
,@forms))

然后,signal函数通过集群;如果它为正确的条件类型找到一个,它就会调用相关的函数。定义:

(defun signal (datum &rest arguments)
(let ((condition (coerce-to-condition datum arguments :default 'simple-condition))
(*handler-clusters* *handler-clusters*)) ; save current value
(when (typep condition *break-on-signals*)
(with-simple-restart (continue "Continue the signaling process")
(break "Break caused by *BREAK-ON-SIGNALS*")))
(loop for cluster := (pop *handler-clusters*) do
(loop for binding in cluster do
(when (typep condition (car binding))
(funcall (cdr binding) condition)))))
nil)

其中coerce-to-condition是处理"条件指示符"的函数。一个微妙的点是,我们不能简单地(loop for cluster in *handler-clusters* do …),因为如果在调用处理程序的过程中,发出与正在处理的条件相同类型的条件的信号,那么处理程序将被递归调用,这可能是不可取的。因此,之前的值被保存,我们破坏性地pop集群关闭。

现在,请记住Common Lisp允许对block名称和tagbody标记进行闭包。也就是说,在定义之后

(defvar *transfer-control*)
(defun weird (function)
(tagbody
(go :start)
:tag
(print 'transferred)
(return-from weird)
:start
(setf *transfer-control* (lambda () ; captures the tag :tag
(go :tag)))
(funcall function)))

类似的形式

(weird (lambda () (funcall *transfer-control*)))

允许,并将打印'transferred。从某种意义上说,控制转移发生在tagbody的词汇范围之外;CCD_ 14的能力已经"逃脱"了它的封闭范围。(由于tagbody的动态范围已退出,所以在weird返回后调用*transfer-control*将是一个错误。)

所有这些都意味着,调用一个普通的Common Lisp函数可以导致控制权的转移,而不是返回一个值。调用*transfer-control*只会将动态环境展开到tagbody,然后跳到:tag。函数不会按照通常的术语"返回",因为它所嵌入的表达式的求值将突然停止,永远不会恢复。(对于weird*transfer-control*,我们定义了catchthrow的基元替换,它只传递控制权,但不同时传递值。要查看tagbodyblockcatch的相互定义,请参阅Henry Baker的"Common Lisp Special Forms的元循环语义"。)

因此,当signal调用条件的处理程序时,可能会发生两件事:

  1. 处理程序转移控制,中止对signal的评估,并将堆栈展开到转移控制的位置
  2. 处理程序不传递控制权,但返回一个值。在这种情况下,正如上面的定义所示,signal将继续寻找一个处理程序,直到到达*handler-clusters*的末尾,或者直到另一个处理进程转移控制权这被称为"衰退">

(在某种程度上,它也可以两者都不做,也可以两者兼而有之,例如,在另一个条件下调用signal。规范称之为延迟。)

例如,超规范给出了handler-case的样本展开。表单

(handler-case form
(type1 (var1) . body1)
(type2 (var2) . body2) ...)

成为(忽略变量捕获问题)

(block return-point
(let ((condition nil))
(tagbody
(handler-bind ((type1 #'(lambda (temp)
(setq condition temp)
(go :handler-tag-1)))
(type2 #'(lambda (temp)
(setq condition temp)
(go :handler-tag-2)))
...)
(return-from return-point form))
:handler-tag-1
(return-from return-point (let ((var1 condition)) . body1))
:handler-tag-2
(return-from return-point (let ((var2 condition)) . body2))
...)))

(我已经重写了hyperspec的代码,使其可读性更强,尽管不卫生,而且我还修复了原始代码中的一个错误。)

正如您所看到的,handler-case建立的处理程序在被调用时无条件地传输控制权。因此,handler-case处理程序肯定能够"处理"条件。

重新启动是以非常相似的方式实现的,restart-bind设置动态环境,invoke-restart使用它来调用函数。因为重新启动只是函数,它们不需要转移控制权,因此调用invoke-restart并不总是一种"处理"行为,尽管如果重新启动是由restart-casewith-simple-restart建立的,当然,如果重新启动转移了控制权。

词汇表准确而简洁地描述了一些事情:如果处理程序正常返回,它就拒绝处理该条件。为了处理该条件,它必须转移控制权,这样它就永远不会返回。在CL中,这意味着它必须"向上"转移控制权。

就是一个例子

(block done
(handler-bind ((error (lambda (c)
(return-from done c))))
(error "exploded")))

这里,类型为error的条件的处理程序正在处理该条件,因为它从不正常返回,而是从done块返回。

这方面的完整描述在这里。


对任何缩进/paren错误表示歉意:我的lisp机器冒烟了,所以我在一个更原始的系统上键入它。

相关内容

最新更新