【问题标题】:What determines the number and types of values a continuation can be applied to?什么决定了延续可以应用到的值的数量和类型?
【发布时间】:2019-12-13 08:06:48
【问题描述】:

Scheme 编程语言

Scheme 允许任何表达式的延续被捕获 程序call/cccall/cc 必须通过一个过程 p 的 一个论点。 call/cc 构造一个具体的表示 当前延续并将其传递给p。延续本身是 由过程k 表示。每次将k 应用于一个值, 它将值返回给call/cc 应用程序的延续。 这个价值在本质上变成了应用的价值 call/cc如果p 在没有调用k 的情况下返回,则返回值 程序变成call/cc的应用值。

延续本身由过程表示。每次将此过程应用于零个或多个值,它 将值返回给call/cc 应用程序的延续。也就是说,当继续程序 被调用时,它会将其参数作为call/cc 的应用程序的值返回。

考虑call/cc(p)p(k)

既然“k作为一个过程可以应用到零个或多个值”,那么决定k可以应用到的值的数量和类型是什么?

由于call/cc 的应用程序的值取决于p 是否在调用k 的情况下返回,因此必须符合以下条件:

  • k 可以应用的值的数量和类型,以及
  • p 返回的值的数量和类型?

【问题讨论】:

  • 不,他们不需要相互同意:(let ((l (call-with-values (thunk (call/cc identity)) list))) (case (length l) ((1) ((first l) 24 (first l))) ((2) ((second l) (first l) (first l) (first l))) ((3) 42)))
  • @PetSerAl 谢谢。你能写一个更详细的答案吗?

标签: scheme lisp racket continuations


【解决方案1】:

我不确定我是否完全理解了这个问题。但是考虑这样的形式

(call/cc p)

p 必须是单个参数的函数,并且该参数将绑定到表示延续的过程。我们可以通过将p 替换为显式的lambda 形式来明确表示:

(call/cc (λ (k)
           ...))

现在,k 本身就是一个过程,当被调用时

将放弃稍后生效的任何延续,而是使用创建逃生过程时生效的延续。 [...] 转义过程接受与对 [call/cc] 的原始调用的延续相同数量的参数。

(来自R5RS)。

(警告,向前看:在我指出的地方进一步阅读。)

如果您已经花足够的时间从延续的角度考虑编程语言,那么这句话是有道理的。如果你没有,或者如果你像我一样每次看到它都必须解决它,那么关键的见解是调用延续正在返回:根据延续指定的语言,如 Scheme 是, 有一些函数,而不是返回,而是简单地调用表示“函数返回后程序的其余部分”的延续,将它们想要返回的值传递给它。

那么,您可以考虑将调用k 视为立即从对创建kcall/cc 的调用返回,因为这就是'[使用] 延续这是在创建逃生程序时生效的'手段。

所以,好吧,我们需要以某种方式向k 提供一些信息,告诉它要返回什么值,而这些信息就是它的参数。所以,例如:

(call/cc (λ (k)
           (k 1)))

将返回 1(当使用值 1 调用 call/cc 以使用正确的语言时,将调用适当的延续)。

而“放弃任何有效的延续”的语言意味着它立即返回。比如

(call/cc (λ (k)
           (k 1)
           (display "not reached")))

不显示not reached,和

(define (maybe-explode-the-bombs check?)
  (if (check? 'really-explode?)
      (explode-the-bombs)
      (dont-explode-the-bombs)))

(call/cc maybe-explode-the-bombs)

既不引爆也不引爆炸弹,而是返回really-explode?

所以,好吧,这就是我所说的谎言:我假设延续接受一个参数,或者等效地函数返回一个值。在许多语言中都是这样,但在 Scheme(或 Common Lisp)中却不是这样:一些延续可以接受零或多个参数,或者等效地,某些函数可以返回零或多个值。

这在 Scheme 中可能发生的地方非常有限:一个延续需要准确地获得它期望的参数数量,并且只有一种方法可以制作一个预期的延续而不是单个参数(这可能是我错了:我不是计划标准的人)。创建期望不是一个参数的延续(aka 允许函数返回一个值以外的值)的方法是call-with-values 和返回一个值以外的方法(调用你的延续与其他不止一个论点...)是values。所以

(call-with-values f1 f2)

调用f1,不带参数,接续接受与f2 相同数量的参数。当调用该延续时,将使用其参数调用 f2。所以

(call-with-values
 (λ () 1)
 (λ (a) a))

是返回1的精巧方式,而

(call-with-values
 (λ ()
   (values 1 2))
 (λ (a1 a2)
   (cons a1 a2)))

返回(1 . 2)

而且你可以构建对他们得到的参数数量不感兴趣的延续:

(call-with-values
 (λ ()
   (values 1 2 3))
 (λ a a))

例如。我怀疑大多数实现在他们的顶级 REPL 中都有这样的东西,所以他们可以打印所有返回的值而不用大惊小怪。

这与call/cc 有何关系?好吧:我说的谎言是代表当前延续的过程,上面的k,返回了一个值,因此需要一个参数。这不是真的:k 可以用任意数量的参数调用,并且当创建它的call/cc 被调用时,它会将所有参数传递给适当的延续。换句话说,k 立即将其所有参数作为来自调用它的call/cc 的值返回。所以:

(define (silly f)
  (f 1 2))

(call-with-values
 (λ ()
   (call/cc silly)
   1)
 (λ (a b)
   (cons a b)))

返回(1 . 2),即使它看起来应该是一个错误,因为它永远不会到达它想要返回单个值的第一个匿名函数中的位置。

正如我上面所说,REPL 通常具有一些对值不敏感的功能,因此您可以交互地进行试验:

> (call/cc (λ (k)
             (k 1 2 3)))
1
2
3

所以,回答你的两个具体问题:如果你有一些函数 p 有一个参数,然后你说 (call/cc p),那么这个表单有两种返回方式(可以调用它的延续):

  • p可以正常返回,返回一定数量的值,然后会从call/cc的形式返回;
  • 参数p got 是一个过程,如果调用它,它将立即从call/cc 表单返回,并将其参数作为值返回。

Scheme 是动态类型的,所以返回的东西的类型是否有效取决于程序,在运行时。通常,两种机制之间的返回值 number 需要相同,因为通常,延续需要固定数量的参数。但这并不一定是这样:正如我在上面所展示的,您可以根据自己的喜好构建对它们期望的参数数量一样挑剔或不挑剔的延续。


最后,为了附加价值,你可以把values写成call/cc

(define (values . vals)
  (call/cc (λ (k)
             (apply k vals))))

(显然这样做会很疯狂,但你可以做到。)


最后,所有关于call/cc 的答案都应该包括:

((λ (c)
   (c c))
 (call/cc (λ (c)
            (c c))))

如果您了解它的作用,您就会了解call/cc 的全部恐怖。如果您的机器容易触手,请不要运行此代码。

【讨论】:

  • 恕我直言,最后一段代码看起来更像((λ (c) (c c)) (call/cc call/cc))
  • @PetSerAl:是的,现在我必须再次弄清楚它是如何工作的!
猜你喜欢
  • 2021-10-17
  • 1970-01-01
  • 1970-01-01
  • 2015-02-11
  • 1970-01-01
  • 2014-04-26
  • 1970-01-01
  • 2014-12-21
  • 2019-05-12
相关资源
最近更新 更多