方案:与continuation一起使用时,define和let之间有什么区别

时间:2013-08-07 04:25:56

标签: scheme racket continuations

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

(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 个答案:

答案 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)

这不仅仅在您看来。如果您在R5RSR6RS中执行此操作的行为不同,则违反了该报告。在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定义中,你的定义只是为值创建一个占位符,而你之后调用的函数没有在环境中链接在一起,