【问题标题】:Scheme: What is the difference between define and let when used with continutation方案:与延续一起使用时,define 和 let 有什么区别
【发布时间】:2013-08-08 07:32:39
【问题描述】:

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

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

这行得通,但为什么呢?

感谢您的帮助!

【问题讨论】:

    标签: scheme racket continuations


    【解决方案1】:

    在 Racket 中,每个顶级表达式都用 prompt 包裹。

    由于call/cc 仅“捕获到最近的提示符的当前继续”,在您的第一个示例中,没有捕获任何其他顶级表达式,因此将cont2 应用于#f 只会产生@987654326 @。

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

    【讨论】:

      【解决方案2】:

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

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

      是顶级的 read-eval-print-loop。也就是说,在您的第一个代码 sn-p 中,您一个接一个地输入表达式,然后返回到顶层。如果你这样做了:

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

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

      【讨论】:

      • 实际上,在 Racket 中,即使是 begin 也不足以导致无限循环。
      • 在 Ikarus 中,确实会发生无限循环。
      • 这实际上是有道理的。谢谢@GoZoner。正如 Chris 所说,在 Racket 中它不起作用,但在 Gambit 中它起作用,这是为什么呢?
      • @ChrisJester-Young,顺便说一下,在 Ikarus 这个(let ((n 100000)) (time (/ (factorial n) (factorial (- n 1))))) 需要 11 秒;在球拍 55 秒内。
      • @ShivaWu 这是因为顶层的begin 可以完全像一系列单独的表达式一样解释。一些实现会这样对待它;其他实现将其视为一个表达式。在我刚刚尝试过的 Scheme 编译器中:Ikarus、Guile 和 Larceny 将其视为一个表达式; Racket 将其视为单独的表达式。
      【解决方案3】:

      这不仅仅是你的意见。如果您在R5RSR6RS 中执行此操作的行为不同,则违反了报告。在racket(r5rs 实现)中,它可能违反了,因为我确实测试了他们的plt-r5rs,它显然不会永远循环。

      #lang racket(实现racket的默认语言)不符合任何标准,因此,像perl5,它的行为方式是规范。他们的文档写了a lot about prompt tags,这缩小了延续的范围。

      阅读这个问题时,我想到了arguments against call/cc。我认为有趣的部分是:

      这说明 call/cc 在真实的 Scheme 系统中实现 从不 无论如何都捕获了整个延续:许多 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 编译器后我可能会改变主意,所以当我有的时候我会回来。

      【讨论】:

        【解决方案4】:

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

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

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

        【讨论】:

          猜你喜欢
          • 2020-07-24
          • 2016-01-11
          • 2010-10-20
          相关资源
          最近更新 更多