我想知道以下两个代码之间的区别:
(define cont2 #f)
(call/cc (lambda (k) (set! cont2 k)))
(display "*")
(cont2 #f)
和
(let [(cont #f)]
(call/cc (lambda (k) (set! cont k)))
(display "*")
(cont #f))
在我看来,这两个程序的正确行为应该是无限打印'*'。 但是,第一个只打印一个'*'并退出, 而第二个给出正确的行为。
所以我很困惑。 define
是否有特别的功能
或者延续不是我想的 - 所有以下程序直到程序结束,它似乎有一个边界或什么的。
另一个猜测是,顶级环境是经过特殊处理的,如下所示:
(define (test)
(define cont2 #f)
(call/cc (lambda (k) (set! cont2 k)))
(display "*")
(cont2 #f))
(test)
这有效,但为什么?
感谢您的帮助!
答案 0 :(得分:8)
在Racket中,每个顶级表达式都包含prompt。
由于call/cc
仅“捕获当前延续到最近的提示”,在第一个示例中,没有捕获其他顶级表达式,因此将cont2
应用于{{1} }结果仅为#f
。
此外,在#f
中包装第一个示例不会改变事情,因为顶级begin
隐式拼接其内容,就像它们是顶级表达式一样。
答案 1 :(得分:4)
当你处于顶层时,继续(注意提示字符'>'):
> (call/cc (lambda (k) (set! cont2 k)))
是顶级的read-eval-print-loop。也就是说,在您的第一个代码段中,您逐个输入表达式,然后返回到每个表达式之后的顶层。如果你改为:
(begin
(define cont3 #f)
...
(cont3 #f))
你会得到无限的'*'(因为你只有在完成begin
时才回到顶级)。你的第三个代码片段就是这个例子;你得到无限'*',因为延续不是顶级循环。
答案 2 :(得分:2)
这不仅仅在您看来。如果您在R5RS
或R6RS
中执行此操作的行为不同,则违反了该报告。在racket
(r5rs实现)中,它可能违反了,因为我测试了他们的plt-r5rs
,它显然不会永远循环。
#lang racket
(实现的默认语言racket
)不符合任何标准,因此,如perl5
,它的行为方式是规范。他们的文档写了a lot about prompt tags,这减少了延续的范围。
阅读此问题时会想到arguments against call/cc。我认为有趣的部分是:
它告诉我们在实际的Scheme系统中实现的call / cc 永远不会 无论如何都抓住了整个延续:许多Scheme系统都放了 REPL或线程周围的隐式控制分隔符。这些含蓄 分隔符很容易引人注意:例如,在Petite Chez或 Scheme48,代码
(let ((k0 #f)) (call/cc (lambda (k) (set! k0 k))) (display 0) (k0 #f))
打印无休止的零流。如果我们把每个操作都放在它上面 自己的行(由自己的REPL评估):
(define k0 #f) (call/cc (lambda (k) (set! k0 k))) (display 0) (k0 #f)
输出仅为0 #f。
我不确定我是否反对call/cc
作为一个原始人(我相信你的工具应该让你有可能用脚射击自己)。我觉得在写一个Scheme编译器后我可能会改变主意,所以当我有这个时,我会回到这个。
答案 3 :(得分:0)
这也行,不要问我为什么。 (Call / cc让我大吃一惊)
(define cont2 #f)
((lambda ()
(call/cc (lambda (k) (set! cont2 k)))
(display "*")
(cont2 #f)))
在你的测试中,定义三行是在一起调用的隐式开始结构中,它是在顶层调用的。在我的版本中,我刚刚创建了一个匿名的thunk来调用它自己。在你的第一个cont2定义中,你的定义只是为值创建一个占位符,而你之后调用的函数没有在环境中链接在一起,