Common lisp 不要嵌套defun(使用flet或标签),sbcl REPL不会报告所有情况(警告)

Common lisp 不要嵌套defun(使用flet或标签),sbcl REPL不会报告所有情况(警告),common-lisp,sbcl,Common Lisp,Sbcl,对SBCL 1.3.1进行的试验 我在函数的内部定义函数xx。(最初xx是递归的,并且从xxx闭包中使用了不变量。但是递归在这里并不重要,xx只返回t)xxx只调用xx。因此,xxx也将返回t xxx在函数内部被调用两次call xxx。要使此问题(条件的问题)显化,必须调用两次。虽然调用之间没有共享状态,但奇怪的是,为什么这会很重要 call xxx作为#“call-xxx传递,在处理程序案例中应用。它不需要参数,所以只应用于nil处理程序案例将其拉出并表示它抛出了一个条件 如果在xxx之外定

对SBCL 1.3.1进行的试验

我在函数的内部定义函数
xx
。(最初
xx
是递归的,并且从
xxx
闭包中使用了不变量。但是递归在这里并不重要,
xx
只返回
t
xxx
只调用
xx
。因此,
xxx
也将返回
t

xxx
在函数内部被调用两次
call xxx
。要使此问题(条件的问题)显化,必须调用两次。虽然调用之间没有共享状态,但奇怪的是,为什么这会很重要

call xxx
作为
#“call-xxx
传递,在
处理程序案例中应用。它不需要参数,所以只应用于nil<代码>处理程序案例
将其拉出并表示它抛出了一个条件

如果在
xxx
之外定义了
xx
,则不存在任何条件,并返回t的预期结果。下面代码的第二部分显示了这一点,其中我们有
ww
www
调用www
,等等。同样,当未使用
处理程序案例时,REPL也不会报告任何异常

[此示例从测试框架中删除,因此当
调用xxx
被称为抛出异常时,测试失败。然而,当测试手动运行时(请参见
运行调用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
* 
这就是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 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))
  • 公共Lisp中的
    defun
    定义了顶级定义。即使它出现在另一个函数中(这种方式不同于Scheme的
    define
    )。因此,每次调用
    xxx
    时都会重新定义函数
    xx
  • 条件
    Condition
    甚至会捕获一些“不值得一提”的内容(请参见
    handler case
    )。由于重新定义
    xx
    会产生警告,因此它将捕获该警告。如果不执行此操作,则REPL中将显示
    处理程序案例
    警告,但会生成正确的结果

  • 我认为这里的问题是,无论发生什么情况,你都在拦截任何条件。在这种情况下,SBCL会发出一个条件,即使用
    defun
    重新定义
    xx
    (不是错误,而是一个警告,因为这可能是意外的)

    将代码更改为:

    (defun test (test-function)
      (handler-case (apply test-function '())
        (condition (x)
          (print x)
          ':exception)))
    
    将显示
    #

    逻辑上的问题是,你不应该声明能够处理你不知道的条件:你应该处理你知道的条件,让其他人由谁知道的人来处理。适当的处理程序可以通过让程序继续运行来解决问题(这里就是这种情况)


    这是条件和异常之间的主要区别:只有在处理程序确定这是正确的操作时,才会在稍后执行解除。用C++来代替,一旦抛出异常,在处理异常时,所有的调用堆栈级别都已经丢失。< / P>。在这一点中,正确定义另一个函数的方法是使用<代码>标签< /> >或<代码> FLe> <代码>。它们的工作原理很像
    let
    ,但使用函数而不是变量。REPL中不会显示警告-如果它有我的帖子,则会有所不同。是否有SBCL开关或我应该抛出的东西来查看此警告?我已经编辑添加了上面的成绩单。杰基斯基已经预料到了我的下一个问题。(哇,非常好的信息,谢谢!)@user244488老实说,我不知道警告开关。我的粘液默认设置产生
    ;SLIME 2015-06-01 STYLE-WARNING:在DEFUN中重新定义COMMON-LISP-USER::XX和在REPL中重新定义COMMON-LISP-USER::TEST.你是MOSTEK人吗?是的,谢谢你上面的打印表格,这使它更清楚了。这是将进入测试台的修复。测试台正确地指出了原始代码存在问题(我只是不理解发生了什么),因为在每次调用时重新定义函数不是一件好事。但是,难道测试台不适合捕捉所有东西吗?
    
    (defun test (test-function)
      (handler-case (apply test-function '())
        (condition (x)
          (print x)
          ':exception)))