![](/img/trans.png)
[英]Can the function argument to `call/cc` equivalently either invoke the continuation or return without invoking the continuation?
[英]Can any case of using call/cc be rewritten equivalently without using it?
任何使用call/cc
可以在不使用的情况下等效地重写吗?
例如
在(g (call/cc f))
,是的目的f
评估一些的值expression
,所以g
可应用于值?
是(g (call/cc f))
总是能够在没有call/cc
情况下等效地重写,例如(g expression)
?
在((call/cc f) arg)
, f
的目的是评估某些函数g
的定义,以便函数g
可以应用于arg
的值?
是((call/cc f) arg)
总是能够在不call/cc
情况下等效地重写(g arg)
例如(g arg)
?
如果答案是肯定的,为什么我们需要使用call/cc
? 我试图理解使用call/cc
的目的,将其与不使用它进行对比。
这里直接回答的关键是“图灵对等”的概念。 也就是说,基本上所有常用的编程语言(C,Java,Scheme,Haskell,Lambda Calculus等等)都是等价的,对于其中一种语言中的任何一种语言,每种语言中都有相应的程序。具有相同含义的其他语言。
除此之外,这些等价物中的一些可能是“好的”而有些可能非常可怕。 这表明我们重新构思了这样一个问题:哪些功能可以用“漂亮”的方式重写为没有该功能的语言,哪些不能?
对此的正式处理来自Matthias Felleisen,他在1991年的论文“关于编程语言的表达能力”( https://www.sciencedirect.com/science/article/pii/016764239190036W )中引入了宏观可表达性的概念。 ,指出某些功能可以用本地方式重写,有些功能需要全局重写。
你原来问题的答案显然是肯定的。 Scheme是Turing-complete,有或没有call/cc
,所以即使没有call/cc
,你仍然可以计算任何可计算的东西。
为什么“比使用lambda编写等效表达式更方便”?
Matthias Felleisen撰写的关于编程语言表达能力的经典论文给出了这个问题的一个答案。 好吧,要将一个带有call/cc
的程序重写为没有它的程序,你可能需要转换整个程序(全局转换)。 这是为了对比其他一些只需要局部转换(即可以写成宏)的构造来移除它们。
关键是: 如果您的程序是以延续传递方式编写的,则不需要call/cc
。 如果没有,祝你好运。
我全心全意地推荐:
丹尼尔P.弗里德曼。 “Continuations的应用:邀请教程”。 1988年编程语言原则(POPL88)。 1988年1月
https://cs.indiana.edu/~dfried/appcont.pdf
如果您喜欢阅读该论文,请查看: https : //github.com/scheme-live/bibliography/blob/master/page6.md
当然,使用call/cc
编写的任何内容都可以在没有它的情况下编写,因为Scheme中的所有内容最终都是使用lambda
编写的。 您使用call/cc
因为它比使用lambda
编写等效表达式更方便。
这个问题有两种感觉:一种无趣的感觉和一种有趣的感觉:
无趣的一个。 是否有一些计算可以用call/cc
做,你不能用没有它的语言做?
不,没有: call/cc
没有使语言更加强大:着名的情况是只有λ和功能应用的语言相当于通用的图灵机,因此没有(已知的)。 ..)更强大的计算系统。
但从编程语言设计的角度来看,这有点无趣:受内存和c的正常限制,几乎所有编程语言都等同于UTM,但人们仍然喜欢使用不涉及打孔的语言纸胶带,如果他们可以。
有趣的一个。 call/cc
会使编程语言的一些理想特性更容易表达?
答案是肯定的,确实如此。 我只想举几个例子。 假设您希望在您的语言中使用某种非本地退出功能,因此一些深度嵌套的程序可以简单地说“我想要这个地狱”,而不必通过一些很好的层来爬回来职能。 使用call/cc
这是微不足道的 :延续过程是转义过程。 如果你希望它更好,你可以用一些语法包装它:
(define-syntax with-escape
(syntax-rules ()
[(_ (e) form ...)
(call/cc (λ (e) form ...))]))
(with-escape (e)
... code in here, and can call e to escape, and return some values ...)
你可以在没有call/cc
情况下实现吗? 嗯,是的,但不是没有依赖于其他一些特殊构造(比如CL中的block
和return-from
),或者没有以某种方式将语言转换为内部。
你可以建立这样的东西来实现各种非本地转义。
或者,好吧,假设您想要GO TO(以下示例是Racket):
(define (test n)
(define m 0)
(define start (call/cc (λ (c) c)))
(printf "here ~A~%" m)
(set! m (+ m 1))
(when (< m n)
(start start)))
或者,使用一些语法:
(define-syntax-rule (label place)
(define place (call/cc identity)))
(define (go place)
(place place))
(define (horrid n)
(define m 0)
(label start)
(printf "here ~A~%" m)
(set! m (+ m 1))
(when (< m n)
(go start)))
所以,好吧,这可能不是编程语言的理想特征。 但是,好吧,Scheme没有GO TO,但是,在这里,它确实如此。
所以,是的, call/cc
(特别是与宏结合使用时)可以表达编程语言的许多理想特性。 其他语言都有这些特殊目的,有限的黑客攻击,Scheme具有这种通用的东西,所有这些特殊用途的黑客都可以用来构建。
问题是call/cc
并没有停止使用好的特殊用途黑客:你还可以构建所有可怕的恐怖,这些恐怖曾经用于破坏编程语言。 call/cc
就像是可以接触到一位上帝的神:如果你想要恐惧力量,这真的很方便,但是当你打电话时,你最好小心一下,因为它可能是超越时空的无法形容的恐怖。
很容易使用call/cc
作为纾困。 例如。
;; (1 2) => (2 4)
;; #f if one element is not a number
(define (double-numbers lst)
(call/cc
(lambda (exit)
(let helper ((lst lst))
(cond ((null? lst) '())
((not (number? (car lst))) (exit #f))
(else (cons (* 2 (car lst)) (helper (cdr lst)))))))))
所以要理解这一点。 如果我们正在做(double-numbers '(1 2 r))
,结果是#f
,但帮助器已经完成(cons 1 (cons 2 (exit #f)))
如果没有call/cc
我们会看到延续将是所谓的double-numbers
因为它实际上从它返回。 这是一个没有call/cc
的示例:
;; (1 2) => (2 4)
;; #f if one element is not a number
(define (double-numbers lst)
(define (helper& lst cont)
(cond ((null? lst) (cont '()))
((not (number? (car lst))) #f) ; bail out, not using cont
(else (helper& (cdr lst)
(lambda (result)
(cont (cons (* 2 (car lst)) result)))))))
(helper& lst values)) ; values works as an identity procedure
我想它变得越来越难。 例如。 我的发电机实施 。 生成器依赖于访问continuation以将生成器代码与其使用位置混合,但是如果没有call/cc
,则需要在生成器,生成的生成器和使用它的代码中执行CPS。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.