不要嵌套 defun(使用 flet 或标签),sbcl REPL 不会报告所有情况(警告)
do not nest defun (use flet or labels), sbcl REPL does not report all conditions (warnings)
在 SBCL 1.3.1 上执行的测试
我在函数 xxx
中定义了函数 xx
。 (最初 xx
是递归的,并且从 xxx
闭包中使用了不变量。但是递归在这里并不重要, xx
只是 returns t
) xxx
只调用 xx
。因此 xxx
预计也会 return t
。
xxx
在函数 call-xxx
中被调用了两次。要显示此问题(条件的问题),必须调用两次。虽然调用之间没有共享状态,但为什么这很重要很好奇。
call-xxx
作为 #'call-xxx
传递以在 handler-case
内应用。它不需要任何参数,所以它只是应用于 nil。 handler-case
把它拉出来说它抛出一个条件。
当 xx
在 xxx
之外定义时,没有条件,并且 t 的预期结果是 returned。这在下面显示的代码的第二部分中显示,其中我们有 ww
、www
、call-www
等。此外,当 handler-case
未使用时,没有REPL 报告的异常。
[这个例子是从测试框架中删除的,因此当 call-xxx
被认为抛出异常时,测试失败。然而,当测试手动运行时(请参阅 run-call-xx
),它会毫无例外地通过,从而产生矛盾并使调试明显失败的测试变得困难。]
造成这种情况的原因是什么?条件处理程序调用是否应该不同?这是 SBCL 错误吗?
代码如下:
(defun test (test-function)
(handler-case (apply test-function '()) (condition () ':exception))
;;; (apply test-function '()) ;; returns t, no exception
)
;;---------------------------------------------------------------
;; throws exception, but shouldn't (?)
;;
(defun xxx ()
(defun xx () t) ; note comments, should be a flet or labels form
(xx))
(defun call-xxx ()
(xxx) ;; #'xxx must be called twice for the exception to appear
(xxx)
t)
;; call-xxx throws exception when run from test, but shouldn't
(defun run-test-call-xxx ()
(test #'call-xxx))
;; no problem here, call-xxx returns t when called directly
(defun run-call-xxx ()
(call-xxx))
;;--------------------------------------------------------
;; works fine
;; pulled out the nested definition of #'ww from #'www
;;
(defun ww () t)
(defun www ()
(ww))
(defun call-www ()
(www)
(www)
t)
(defun run-test-call-www ()
(test #'call-www))
这里是 运行:
§sbcl> sbcl
This is SBCL 1.3.1.debian, an implementation of ANSI Common Lisp.
More information about SBCL is available at <http://www.sbcl.org/>.
SBCL is free software, provided as is, with absolutely no warranty.
It is mostly in the public domain; some portions are provided under
BSD-style licenses. See the CREDITS and COPYING files in the
distribution for more information.
* (load "src/test-xxx")
T
* (run-test-call-xxx)
:EXCEPTION
* (run-test-call-www)
T
*
added 这里是重新定义的测试函数,用于在没有处理程序的情况下进行回复。我这样做是为了查看 REPL 报告的内容。
(defun test (test-function)
;;; -> this does not get handled
;;; (handler-case (apply test-function '()) (serious-condition () ':exception))
;;; -> this triggers the handler
;;; (handler-case (apply test-function '()) (condition () ':exception))
(apply test-function '()))
这就是 REPL 中发生的事情:
§sbcl> sbcl
This is SBCL 1.3.1.debian, an implementation of ANSI Common Lisp....
distribution for more information.
* (load "src/test-handler.lisp")
T
* (run-test-call-xxx)
T
*
如您所见,REPL 没有打印警告。
注意注释,defun
只是顶层,所以使用 flet
处理闭包中的递归。 defun
内部函数将在每个条目上定义,尽管发出警告,但不会进入 REPL(尽管处理程序案例确实看到它,这就是这里发生的事情)
(defun f (s too-big-for-stack-invariant)
(defun r (s) ;; note comments, should be a labels form, not defun
;; changes s and checks for termination
;; makes use of the very large invariant data
...
r(s))
;; some stuff making use of r(s)
)
代码的两个问题:
Common Lisp 中的 defun
定义了 top-level 定义。即使它出现在另一个函数内部(这种方式与 Scheme 的 define
不同)。因此,每次调用 xxx
时都会重新定义函数 xx
。
- 条件
condition
甚至可以捕获某些东西 "not worth mentioning"(参见 handler-case
上的 CLHS)。由于 xx
的重新定义会产生警告,因此它会捕获它。如果您不这样做,handler-case
警告将显示在 REPL 中,但会产生正确的结果。
我认为这里的问题是无论如何你都在拦截任何条件。在这种情况下,SBCL 发出关于 xx
被重新定义为 defun
的条件(不是错误,而是警告,因为它可能是无意的)。
将代码更改为:
(defun test (test-function)
(handler-case (apply test-function '())
(condition (x)
(print x)
':exception)))
将显示 #<SB-KERNEL:REDEFINITION-WITH-DEFUN {1002BE3E93}>
合乎逻辑的问题是,你不应该声明能够处理你不知道的情况:你应该处理你知道的情况,让其他人由知道的人来处理。条件可以由适当的处理程序通过让程序继续运行来解决运行(就像这里的情况一样)。
这是条件和异常之间的主要区别:仅当处理程序认为它是正确的事情时才执行展开。用 C++ 代替,一旦抛出异常,当异常被处理时,所有都已经在较低的调用堆栈级别丢失。
在 SBCL 1.3.1 上执行的测试
我在函数 xxx
中定义了函数 xx
。 (最初 xx
是递归的,并且从 xxx
闭包中使用了不变量。但是递归在这里并不重要, xx
只是 returns t
) xxx
只调用 xx
。因此 xxx
预计也会 return t
。
xxx
在函数 call-xxx
中被调用了两次。要显示此问题(条件的问题),必须调用两次。虽然调用之间没有共享状态,但为什么这很重要很好奇。
call-xxx
作为 #'call-xxx
传递以在 handler-case
内应用。它不需要任何参数,所以它只是应用于 nil。 handler-case
把它拉出来说它抛出一个条件。
当 xx
在 xxx
之外定义时,没有条件,并且 t 的预期结果是 returned。这在下面显示的代码的第二部分中显示,其中我们有 ww
、www
、call-www
等。此外,当 handler-case
未使用时,没有REPL 报告的异常。
[这个例子是从测试框架中删除的,因此当 call-xxx
被认为抛出异常时,测试失败。然而,当测试手动运行时(请参阅 run-call-xx
),它会毫无例外地通过,从而产生矛盾并使调试明显失败的测试变得困难。]
造成这种情况的原因是什么?条件处理程序调用是否应该不同?这是 SBCL 错误吗?
代码如下:
(defun test (test-function)
(handler-case (apply test-function '()) (condition () ':exception))
;;; (apply test-function '()) ;; returns t, no exception
)
;;---------------------------------------------------------------
;; throws exception, but shouldn't (?)
;;
(defun xxx ()
(defun xx () t) ; note comments, should be a flet or labels form
(xx))
(defun call-xxx ()
(xxx) ;; #'xxx must be called twice for the exception to appear
(xxx)
t)
;; call-xxx throws exception when run from test, but shouldn't
(defun run-test-call-xxx ()
(test #'call-xxx))
;; no problem here, call-xxx returns t when called directly
(defun run-call-xxx ()
(call-xxx))
;;--------------------------------------------------------
;; works fine
;; pulled out the nested definition of #'ww from #'www
;;
(defun ww () t)
(defun www ()
(ww))
(defun call-www ()
(www)
(www)
t)
(defun run-test-call-www ()
(test #'call-www))
这里是 运行:
§sbcl> sbcl
This is SBCL 1.3.1.debian, an implementation of ANSI Common Lisp.
More information about SBCL is available at <http://www.sbcl.org/>.
SBCL is free software, provided as is, with absolutely no warranty.
It is mostly in the public domain; some portions are provided under
BSD-style licenses. See the CREDITS and COPYING files in the
distribution for more information.
* (load "src/test-xxx")
T
* (run-test-call-xxx)
:EXCEPTION
* (run-test-call-www)
T
*
added 这里是重新定义的测试函数,用于在没有处理程序的情况下进行回复。我这样做是为了查看 REPL 报告的内容。
(defun test (test-function)
;;; -> this does not get handled
;;; (handler-case (apply test-function '()) (serious-condition () ':exception))
;;; -> this triggers the handler
;;; (handler-case (apply test-function '()) (condition () ':exception))
(apply test-function '()))
这就是 REPL 中发生的事情:
§sbcl> sbcl
This is SBCL 1.3.1.debian, an implementation of ANSI Common Lisp....
distribution for more information.
* (load "src/test-handler.lisp")
T
* (run-test-call-xxx)
T
*
如您所见,REPL 没有打印警告。
注意注释,defun
只是顶层,所以使用 flet
处理闭包中的递归。 defun
内部函数将在每个条目上定义,尽管发出警告,但不会进入 REPL(尽管处理程序案例确实看到它,这就是这里发生的事情)
(defun f (s too-big-for-stack-invariant)
(defun r (s) ;; note comments, should be a labels form, not defun
;; changes s and checks for termination
;; makes use of the very large invariant data
...
r(s))
;; some stuff making use of r(s)
)
代码的两个问题:
-
Common Lisp 中的
defun
定义了 top-level 定义。即使它出现在另一个函数内部(这种方式与 Scheme 的define
不同)。因此,每次调用xxx
时都会重新定义函数xx
。- 条件
condition
甚至可以捕获某些东西 "not worth mentioning"(参见handler-case
上的 CLHS)。由于xx
的重新定义会产生警告,因此它会捕获它。如果您不这样做,handler-case
警告将显示在 REPL 中,但会产生正确的结果。
我认为这里的问题是无论如何你都在拦截任何条件。在这种情况下,SBCL 发出关于 xx
被重新定义为 defun
的条件(不是错误,而是警告,因为它可能是无意的)。
将代码更改为:
(defun test (test-function)
(handler-case (apply test-function '())
(condition (x)
(print x)
':exception)))
将显示 #<SB-KERNEL:REDEFINITION-WITH-DEFUN {1002BE3E93}>
合乎逻辑的问题是,你不应该声明能够处理你不知道的情况:你应该处理你知道的情况,让其他人由知道的人来处理。条件可以由适当的处理程序通过让程序继续运行来解决运行(就像这里的情况一样)。
这是条件和异常之间的主要区别:仅当处理程序认为它是正确的事情时才执行展开。用 C++ 代替,一旦抛出异常,当异常被处理时,所有都已经在较低的调用堆栈级别丢失。