call/cc是否以这种方式模拟goto?

3
在书籍《Lisp in Small Pieces》中,有一段示例代码,旨在演示call/cc可以模拟goto。
(define (fact n)
   (let ((r 1) (k 'void))
      (call/cc (lambda (c) (set! k c) 'void))
      (set! r (* r n))
      (set! n (- n 1))
      (if (= n 1) r (k 'recurse))))

然而,我不确定自己是否误解了什么,但我看不出这是 call/cc 模拟 goto 的方式。当在最后一行应用 k 时,恢复的 continuation 具有原始 continuation 的 rn,它们的值由两个 set! 应用程序未改变。因此,整个循环将永远不会终止。
这个例子中的书写有误吗?或者是我漏掉了什么?
1个回答

1
恢复的 continuation 具有原始 continuation 的 r 和 n,它们的值不会被两个 set! 应用程序改变。这是重要的部分;值的更改是可见的,它们没有被重置。我不确定这个问题是否应该被认为是重复的,但在 call-with-current-continuation - state saving concept 中也出现了这个问题,其中提问者注意到(请查看问题的整个上下文):连续调用 next 3 次会产生 0、1 和 'done。这意味着当状态使用生成器给出的函数 k 时,它没有恢复程序的状态。您可以通过保存 continuation 后打印 r 和 n 的值来非常简单地测试这一点。您将看到更新后的值在那里。例如:
(define (fact n)
  (let ((r 1) (k 'void))
    (call-with-current-continuation (lambda (c) (set! k c) 'void))
    (display "r: ") (display r) (newline)
    (display "n: ") (display n) (newline)
    (set! r (* r n))
    (set! n (- n 1))
    (if (= n 1) r (k 'recurse))))

> (fact 6)
r: 1
n: 6
r: 6
n: 5
r: 30
n: 4
r: 120
n: 3
r: 360
n: 2
720

相关问题:

还可以参考:


1
@NateC-K 嗯,是的也不是的。call/cc有点特殊,因为它保存了整个程序状态,但正如我们所看到的,当保存程序状态时,它并没有复制环境。很少有语言像call/cc一样具有“保存整个程序状态”的操作,我认为,结果通常被想象成是对程序进行快照。问题在于它并不是真正的快照。它就像将其余的计算捆绑在当前环境的闭包中。这就是许多人产生分歧的地方。 - Joshua Taylor
这就是我想表达的,这就是为什么我建议这个问题的答案应该提到闭包的某个部分。(你的答案没有提到,你链接的答案也没有。) - Nate C-K
1
@DongFeng 当你在使用高级语言Scheme时,放弃“分配在堆栈上”和类似的语义。关键是set!会改变绑定的,因此所有封闭在这些绑定上的作用域都会看到set!的结果。 - Alexis King
@DongFeng 是的,就像Alexis King所说的那样,栈分配或堆分配的概念并不重要(尽管它仍然与实现相关)。毕竟,如果你返回一个闭包来覆盖一些局部变量(例如,(define (over-x) (let ((x 42)) (lambda () (set! x (+ 1 x)) x)))),你可以继续调用结果并获得更大的数字。这意味着无论x的存储位置在哪里,它所在的环境在over-x返回后必须被保留,因为lambda函数对其进行了封闭。 - Joshua Taylor
我理解并感激您的所有解释。但是,我不想放弃从“分配在堆栈上”的角度理解的机会。幸运的是,我找到了这篇论文《一等续体的实现策略》,其中明确指出,如果将续体实现为复制堆栈,则不允许使用堆栈变量。 - Dong Feng
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接