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

为什么Racket中这段continuation调用会触发无限循环?

问题复现

你用到的测试代码如下:

(define (f x)
  (let ((cont null))
    (println (+ 10 (call/cc (lambda (cc)                              
                              (set! cont cc)
                              10))))
    cont))

第一种写法:((f 1) 10) 无限打印20的原因

我们一步步拆解这个表达式的执行逻辑:

  • 第一步:求值整个表达式的左半部分(f 1):
    1. 进入f函数体,初始化cont为null
    2. 执行println的参数部分,遇到call/cc,此时捕获的续延cc代表的上下文是:将call/cc的返回值加10,传给println打印,然后函数f返回这个续延本身
    3. 把cc赋值给cont后,call/cc首次返回10,计算10+10=20,打印20,f返回cont(也就是刚才捕获的续延对象)
  • 第二步:现在左半部分求值结果是cont,右半部分求值结果是10,开始执行(cont 10):
    1. 调用续延cont时,call/cc会直接返回传入的参数10,回到之前捕获的上下文:计算10+10=20,再次打印20,然后f再次返回cont
    2. 关键的循环点来了:你调用cont的本质是完全恢复到(f 1)正在求值的上下文,所以此时(f 1)的求值结果又是cont,外层表达式的逻辑是「把(f 1)的返回值当做函数调用,传入10」,所以会自动再次执行(cont 10),由此进入无限循环。

第二种写法:赋值给顶层变量后执行符合预期的原因

测试代码执行流程:

> (define kont (f 1))
;;=> 20
> (kont 12)
;;=> 22
  • 第一步:执行(define kont (f 1))时,(f 1)的执行逻辑和上面一致,打印20,返回的cont赋值给顶层变量kont,没有后续自动调用逻辑。
  • 第二步:执行(kont 12)时,call/cc返回12,计算10+12=22,打印22,然后f返回cont。此时你是在顶层REPL调用kont,返回的cont只会被REPL当做普通值输出,不会被自动调用,所以执行一次就结束。

核心差异

两种写法的本质区别是续延调用结束后,返回的续延对象是否会被自动再次调用:

  • 第一种写法的表达式结构((f 1) 10)天然要求「f的返回值必须被当做函数调用」,而调用续延会让f反复返回续延对象,所以触发无限循环。
  • 第二种写法没有自动调用返回值的逻辑,调用kont后返回的续延不会被再次执行,所以符合预期。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 01:39:00