为什么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):- 进入
f函数体,初始化cont为null - 执行
println的参数部分,遇到call/cc,此时捕获的续延cc代表的上下文是:将call/cc的返回值加10,传给println打印,然后函数f返回这个续延本身 - 把
cc赋值给cont后,call/cc首次返回10,计算10+10=20,打印20,f返回cont(也就是刚才捕获的续延对象)
- 进入
- 第二步:现在左半部分求值结果是
cont,右半部分求值结果是10,开始执行(cont 10):- 调用续延
cont时,call/cc会直接返回传入的参数10,回到之前捕获的上下文:计算10+10=20,再次打印20,然后f再次返回cont - 关键的循环点来了:你调用
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
相关产品推荐
相关产品推荐

