You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Common Lisp重启控制流疑惑:递归调用为何引发重复输出?

Common Lisp中Condition/Restart机制的执行流困惑:为何After low.会打印两次?

重阅《Practical Common Lisp》第19章后,测试Common Lisp的condition与restart机制时遇到以下困惑:

以下代码中,重启会递归调用触发条件的low函数并传入新参数:

(define-condition no-fun-allowed-condition nil nil)

(defun low (value)
  (format t "~&Before low.")

  (if (equal value "fun")
      (restart-case (signal 'no-fun-allowed-condition)
        (some-restart () (low "no-fun")))
      (format t "~&Running low with a non-fun value: '~A'" value))
  
  (format t "~&After low."))

(defun mid ()
  (format t "~&Before mid.")
  (low "fun")
  (format t "~&After mid."))

(defun high ()
  (handler-bind
      ((no-fun-allowed-condition
        #'(lambda (c)
            (declare (ignorable c))
            (invoke-restart 'some-restart))))
    (mid)))

(high)

运行后输出:

Before mid.
Before low.
Before low.
Running low with a non-fun value: 'no-fun'
After low.
After low.
After mid.

Before low因递归打印两次可以理解,但After low也打印两次令我不解。推测是handler-bind不会unwind调用栈,递归调用的low执行完后,原low函数会继续执行,但原本以为重启会拦截控制流。这是否是递归调用加副作用导致的特殊情况?


解答

这不是特殊情况,完全符合Common Lisp Condition/Restart机制的设计逻辑,核心原因在于以下两点:

  1. signal不会中断当前执行流:signal的作用只是通知已注册的条件处理器,它本身不会主动unwind调用栈,也不会终止当前函数的执行。
  2. handler-bind不截断调用栈:handler-bind注册的处理器在触发后,执行完重启动作(这里是递归调用low "no-fun"),程序会回到触发signal的位置,继续执行原函数中剩余的代码。

具体执行流程拆解:

  • 调用high → 进入handler-bind上下文 → 调用mid,打印Before mid.
  • 调用low "fun",打印Before low. → 进入if分支,执行restart-case (signal ...)
  • signal触发no-fun-allowed-condition,handler-bind中的处理器调用invoke-restart 'some-restart
  • 重启动作执行:递归调用low "no-fun" → 打印Before low. → 进入else分支,打印运行信息 → 执行After low.,递归的low返回
  • 回到最初的low "fun"中的restart-case表达式,该表达式的返回值是重启动作的结果 → 继续执行low函数中剩余的(format t "~&After low."),因此第二次打印After low.
  • 最初的low返回 → mid打印After mid.,程序结束

如果希望原low函数在触发重启后不再执行后续代码,可以通过以下方式调整:

  • 用error代替signal:error会主动触发栈unwind,配合restart-case时,重启动作执行后原函数流会被截断
  • 在重启动作中使用return-from显式跳出原low函数:比如把重启定义改为(some-restart () (return-from low (low "no-fun")))
  • 改用handler-case代替handler-bind:handler-case会在处理条件后unwind栈,终止原执行流

内容的提问来源于stack exchange,提问作者lupl

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 06:27:04