4

我想知道以下两个代码之间的区别:

(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)

这行得通,但为什么呢?

谢谢您的帮助!

4

4 回答 4

8

在 Racket 中,每个顶级表达式都包含一个prompt

由于call/cc仅“捕获当前延续到最近的提示”,因此在您的第一个示例中,没有捕获任何其他顶级表达式,因此仅cont2应用于.#f#f

此外,包装第一个示例begin不会改变任何事情,因为顶级begin隐式拼接其内容,就好像它们是顶级表达式一样。

于 2013-08-07T06:41:25.337 回答
4

当你在顶层时,继续(注意提示符'>'):

> (call/cc (lambda (k) (set! cont2 k)))

是顶级的读取评估打印循环。也就是说,在您的第一个代码片段中,您一个接一个地输入表达式,然后返回到顶层。相反,如果您这样做了:

(begin
  (define cont3 #f)
  ...
  (cont3 #f))

你会得到无限的'*'(因为你只有在完成时才回到顶层begin)。您的第三个代码片段就是这样的一个实例;你得到无限的'*',因为延续不是顶级循环。

于 2013-08-07T05:17:28.143 回答
2

这不仅在你看来。如果您这样做R5RSR6RS两者的行为不同,则违反了报告。在racket(r5rs 实现)中,它可能违反了,因为我确实测试了它们plt-r5rs并且它显然不会永远循环。

#lang racket(实现的默认语言racket)不符合任何标准,因此,perl5它的行为方式就是规范。他们的文档写了很多关于提示标签的内容,这些标签减少了延续的范围。

阅读此问题时,会想到一些反对 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 编译器之后改变主意,所以当我有的时候我会回到这个问题上。

于 2013-08-07T21:25:26.333 回答
0

这也有效,不要问我为什么。(打电话/抄送让我大吃一惊)

(define cont2 #f)     
((lambda () 
  (call/cc (lambda (k) (set! cont2 k)))
  (display "*")
  (cont2 #f)))

在您的测试定义中,这三行位于您一起调用的隐式开始结构中,该结构在顶层调用。在我的版本中,我刚刚创建了一个匿名 thunk 来调用它自己。在您对 cont2 的第一个定义中,您的定义只是为该值创建一个占位符,之后您的函数调用没有在环境中链接在一起,

于 2013-08-07T05:13:00.253 回答