使用带有状态闭包的处理程序绑定是否有效?

问题描述

这是一个符合 Common Lisp 的程序吗?

(handler-bind ((condition (let ((x 0))
                            (lambda (c)
                              (declare (ignore c))
                              (print (incf x))))))
  (signal 'condition)
  (signal 'condition))

SBCL (2.0.5.37) 的输出是:

1
1

ABCL/ccl/ECL 的输出是:

1
2

Common Lisp 标准定义了哪些行为?


结语

这是 SBCL 中的一个错误it is now fixed

解决方法

不是很清楚。 spec 说:

在指定的处理程序绑定有效的动态环境中执行表单

然后说

如果找到合适的类型,关联的处理程序将在动态环境中运行,在该环境中这些处理程序绑定都不可见(以避免递归错误)。

如果您将“运行”解释为调用函数,则表明在进行绑定时处理程序表达式会被评估一次。这是 CCL/ABCL/ECL/LispWorks 实现,所以状态在闭包中维护。

但是 SBCL 似乎已经将“运行”解释为“评估和调用”的意思。所以每次运行处理程序时都会创建一个新的闭包,并且状态会丢失。

我怀疑意图是第一种解释,因为 CL 没有其他“惰性”绑定。

如果您将问题中的代码更改为:

(let ((handler
        (let ((x 0))
          (lambda (c)
            (declare (ignore c))
            (print (incf x))))))
  (handler-bind ((condition handler))
    (signal 'condition)
    (signal 'condition)))

然后 SBCL 的行为方式与其他实现方式相同。我认为这清楚地表明其他实现所采用的解释是预期的,并且它还提供了一种实用的解决方法,如果该解释实际上是正确的,则是 SBCL 中的错误。