什么决定了 Scheme 延续的外部边界?

Mat*_*ick 5 scheme continuations racket

延续的解释通常说延续代表“程序的其余部分”(或类似的措辞)。但显然有一个边界,在这个边界上,继续停止收集这些剩余的计算步骤。那边界是什么?程序的最高级别?或者是其他东西?

这些解释往往以这样的玩具示例开始。

(+ 1 (call/cc
      (lambda (cc)
        (cc 2))))
Run Code Online (Sandbox Code Playgroud)

这计算为3因为(cc 2)意味着“放入2call/cc形式雕刻而成的表达式中的孔中” 。表达式变为(+ 1 2),又名3

现在考虑这个例子:

(define lc #f)
(+ 1 (call/cc
      (lambda (cc)
        (set! lc cc)
        (cc 2))))
(displayln "done")
(lc 42)
Run Code Online (Sandbox Code Playgroud)

在这里,我们将延续存储cc在变量 中lc。在计算表达式后,我们done再次显示并使用延续作为(lc 42)。我们得到什么?

3
done
43
Run Code Online (Sandbox Code Playgroud)

但为什么?如果延续是“程序的其余部分”,为什么延续不捕获 之后发生的所有事情call/cc,包括对displayln和的后续调用lc?在这种解释下,延续将创建一个无限循环。

很明显,事实并非如此。相反,延续似乎正在捕获程序的其余部分,直到它到达随后的表达式,它会忽略(以及任何其他表达式)。

但现在考虑这个例子:

3
done
43
Run Code Online (Sandbox Code Playgroud)

这种情况下的结果是:

3
done
outer
43
done
Run Code Online (Sandbox Code Playgroud)

意思是,延续确实捕获了(displayln "done")in f,尽管它仍然没有捕获(displayln "outer")(lc 42)的调用之后f

最后一个例子——我们将所有内容都移动到一个新函数中g

(define lc #f)
(define (f)
  (displayln (+ 1 (call/cc
                   (lambda (cc)
                     (set! lc cc)
                     (cc 2)))))
  (displayln "done"))
(f)
(displayln "outer")
(lc 42)
Run Code Online (Sandbox Code Playgroud)

这一次,我们得到了前面例子中预测的无限循环:

3
done
outer
43
done
outer
43
···
Run Code Online (Sandbox Code Playgroud)

所以最初的直觉并不完全偏离基础。这只是顶级无望的另一个例子吗?或者是否有更简洁的解释来说明延续的范围?

Sor*_*ase 1

据我所知,这是 Racket 有意为之的。州文件module

表达式或定义的每个计算都用默认提示标记的继续提示(请参阅 call-with-continuation-prompt)包装,并使用提示处理程序重新中止并将其参数传播到下一个封闭提示。

我的猜测(可能完全错误)是这样做是为了模仿 REPL 中的行为。

#lang racket
1
2
Run Code Online (Sandbox Code Playgroud)

打印12,就像:

> 1
1
> 2
2
Run Code Online (Sandbox Code Playgroud)

既然我们有了这个,为每个表达式安装提示似乎是下一步要做的合乎逻辑的事情。

其他没有模块系统的实现似乎具有您期望的行为。例如,在 Guile 中,如果运行上述程序(更改displaylndisplaycall/ccto call-with-current-continuation,因为 Guile 没有这些函数),您将得到 的无限循环3doneouter43doneouter43doneouter...

顺便说一句:据我了解,“顶层无望”并不是指模块内的顶层。它指的是模块之外的那些。例如,当您使用 REPL 时,或者当您使用没有模块系统的实现时。