简体   繁体   English

任何使用call / cc的情况都可以在不使用的情况下等效地重写吗?

[英]Can any case of using call/cc be rewritten equivalently without using it?

Can any case of using call/cc be rewritten equivalently without using it? 任何使用call/cc可以在不使用的情况下等效地重写吗?

For example 例如

  1. In (g (call/cc f)) , is the purpose of f to evaluate the value of some expression , so that g can be applied to the value? (g (call/cc f)) ,是的目的f评估一些的值expression ,所以g可应用于值?

    Is (g (call/cc f)) always able to be rewritten equivalently without call/cc eg (g expression) ? (g (call/cc f))总是能够在没有call/cc情况下等效地重写,例如(g expression)

  2. In ((call/cc f) arg) , is the purpose of f to evaluate the definition of some function g , so that function g can be applied to the value of arg ? ((call/cc f) arg)f的目的是评估某些函数g的定义,以便函数g可以应用于arg的值?

    Is ((call/cc f) arg) always able to be rewritten equivalently without call/cc eg (g arg) ? ((call/cc f) arg)总是能够在不call/cc情况下等效地重写(g arg)例如(g arg)

If the answers are yes, why do we need to use call/cc ? 如果答案是肯定的,为什么我们需要使用call/cc I am trying to understand the purpose of using call/cc , by contrasting it to not using it. 我试图理解使用call/cc的目的,将其与不使用它进行对比。

The key to the direct answer here is the notion of "Turing equivalence". 这里直接回答的关键是“图灵对等”的概念。 That is, essentially all of the commonly used programming languages (C, Java, Scheme, Haskell, Lambda Calculus etc. etc.) are equivalent in the sense that for any program in one of these languages, there is a corresponding program in each of the other languages which has the same meaning. 也就是说,基本上所有常用的编程语言(C,Java,Scheme,Haskell,Lambda Calculus等等)都是等价的,对于其中一种语言中的任何一种语言,每种语言中都有相应的程序。具有相同含义的其他语言。

Beyond this, though, some of these equivalences may be "nice" and some may be really horrible. 除此之外,这些等价物中的一些可能是“好的”而有些可能非常可怕。 This suggests that we reframe the question: which features can be rewritten in a "nice" way into languages without that feature, and which cannot? 这表明我们重新构思了这样一个问题:哪些功能可以用“漂亮”的方式重写为没有该功能的语言,哪些不能?

A formal treatment of this comes from Matthias Felleisen, in his 1991 paper "On the Expressive Power of Programming Languages" ( https://www.sciencedirect.com/science/article/pii/016764239190036W ), which introduces a notion of macro expressibility, pointing out that some features can be rewritten in a local way, and some require global rewrites. 对此的正式处理来自Matthias Felleisen,他在1991年的论文“关于编程语言的表达能力”( https://www.sciencedirect.com/science/article/pii/016764239190036W )中引入了宏观可表达性的概念。 ,指出某些功能可以用本地方式重写,有些功能需要全局重写。

The answer to your original question is obviously yes. 你原来问题的答案显然是肯定的。 Scheme is Turing-complete, with or without call/cc , so even without call/cc , you can still compute anything that is computable. Scheme是Turing-complete,有或没有call/cc ,所以即使没有call/cc ,你仍然可以计算任何可计算的东西。

Why "it is more convenient than writing the equivalent expression using lambda"? 为什么“比使用lambda编写等效表达式更方便”?

The classic paper On the Expressive Power of Programming Languages by Matthias Felleisen gives one answer to this question. Matthias Felleisen撰写的关于编程语言表达能力的经典论文给出了这个问题的一个答案。 Pretty much, to rewrite a program with call/cc to one without it, you might potentially need to transform your whole program (global transformation). 好吧,要将一个带有call/cc的程序重写为没有它的程序,你可能需要转换整个程序(全局转换)。 This is to contrast some other constructs that only need a local transformation (ie, can be written as macro) to remove them. 这是为了对比其他一些只需要局部转换(即可以写成宏)的构造来移除它们。

The key is: If your program is written in continuation passing style, you don't need call/cc . 关键是: 如果您的程序是以延续传递方式编写的,则不需要call/cc If not, good luck. 如果没有,祝你好运。

I whole-heartedly recommend: 我全心全意地推荐:

Daniel P. Friedman. 丹尼尔P.弗里德曼。 "Applications of Continuations: Invited Tutorial". “Continuations的应用:邀请教程”。 1988 Principles of Programming Languages (POPL88). 1988年编程语言原则(POPL88)。 January 1988 1988年1月

https://cs.indiana.edu/~dfried/appcont.pdf https://cs.indiana.edu/~dfried/appcont.pdf

If you enjoy reading that paper, then check out: https://github.com/scheme-live/bibliography/blob/master/page6.md 如果您喜欢阅读该论文,请查看: https//github.com/scheme-live/bibliography/blob/master/page6.md

Of course anything that is written with call/cc can be written without it, because everything in Scheme is ultimately written using lambda . 当然,使用call/cc编写的任何内容可以在没有它的情况下编写,因为Scheme中的所有内容最终都是使用lambda编写的。 You use call/cc because it is more convenient than writing the equivalent expression using lambda . 您使用call/cc因为它比使用lambda编写等效表达式更方便。

There are two senses to this question: an uninteresting one and an interesting one: 这个问题有两种感觉:一种无趣的感觉和一种有趣的感觉:

The uninteresting one. 无趣的一个。 Is there some computation that you can do with call/cc that you can't do in a language which does not have it? 是否有一些计算可以用call/cc做,你不能用没有它的语言做?

No, there isn't: call/cc doesn't make a language properly more powerful: it is famously the case that a language with only λ and function application is equivalent to a universal Turing machine, and thus there is no (known...) more powerful computational system. 不,没有: call/cc没有使语言更加强大:着名的情况是只有λ和功能应用的语言相当于通用的图灵机,因此没有(已知的)。 ..)更强大的计算系统。

But that's kind of uninteresting from the point of view of programming-language design: subject to the normal constraints on memory &c, pretty much all programming languages are equivalent to UTMs, but people still prefer to use languages which don't involve punching holes in paper tape if they can. 但从编程语言设计的角度来看,这有点无趣:受内存和c的正常限制,几乎所有编程语言都等同于UTM,但人们仍然喜欢使用不涉及打孔的语言纸胶带,如果他们可以。

The interesting one. 有趣的一个。 Is it the case that call/cc makes some desirable features of a programming language easier to express? call/cc会使编程语言的一些理想特性更容易表达?

The answer to this is yes, it does. 答案是肯定的,确实如此。 I'll just give a couple of examples. 我只想举几个例子。 Let's say you want to have some kind of non-local exit feature in your language, so some deeply-nested bit of program can just say 'to hell with this I want out', without having to climb back out through some great layer of functions. 假设您希望在您的语言中使用某种非本地退出功能,因此一些深度嵌套的程序可以简单地说“我想要这个地狱”,而不必通过一些很好的层来爬回来职能。 This is trivial with call/cc : the continuation procedure is the escape procedure. 使用call/cc这是微不足道的 :延续过程转义过程。 You can wrap it in some syntax if you want it to be nicer: 如果你希望它更好,你可以用一些语法包装它:

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

Can you implement this without call/cc ? 你可以在没有call/cc情况下实现吗? Well, yes, but not without either relying on some other special construct (say block and return-from in CL), or without turning the language inside out in some way. 嗯,是的,但不是没有依赖于其他一些特殊构造(比如CL中的blockreturn-from ),或者没有以某种方式将语言转换为内部。

And you can build on things like this to implement all sorts of non-local escapes. 你可以建立这样的东西来实现各种非本地转义。

Or, well, let's say you want GO TO (the following example is Racket): 或者,好吧,假设您想要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)))

Or, with some syntax around this: 或者,使用一些语法:

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

So, OK, this perhaps is not a desirable feature of a programming language. 所以,好吧,这可能不是编程语言的理想特征。 But, well, Scheme doesn't have GO TO right, and yet, here, it does. 但是,好吧,Scheme没有GO TO,但是,在这里,它确实如此。

So, yes, call/cc (especially when combined with macros) makes a lot of desirable features of a programming language possible to express. 所以,是的, call/cc (特别是与宏结合使用时)可以表达编程语言的许多理想特性。 Other languages have all these special-purpose, limited hacks, Scheme has this universal thing from which all these special-purpose hacks can be built. 其他语言都有这些特殊目的,有限的黑客攻击,Scheme具有这种通用的东西,所有这些特殊用途的黑客都可以用来构建。

The problem is that call/cc doesn't stop with the good special-purpose hacks: you can also build all the awful horrors that used to blight programming languages out of it. 问题是call/cc并没有停止使用好的特殊用途黑客:你还可以构建所有可怕的恐怖,这些恐怖曾经用于破坏编程语言。 call/cc is like having access to an elder god: it's really convenient if you want dread power, but you'd better be careful what comes with it when you call, because it may well be an unspeakable horror from beyond spacetime. call/cc就像是可以接触到一位上帝的神:如果你想要恐惧力量,这真的很方便,但是当你打电话时,你最好小心一下,因为它可能是超越时空的无法形容的恐怖。

An easy use of call/cc is as a bail out. 很容易使用call/cc作为纾困。 eg. 例如。

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

So to understand this. 所以要理解这一点。 If we are doing (double-numbers '(1 2 r)) the result is #f , but the helper has done (cons 1 (cons 2 (exit #f))) 如果我们正在做(double-numbers '(1 2 r)) ,结果是#f ,但帮助器已经完成(cons 1 (cons 2 (exit #f)))

Without call/cc we see the continuation would be whatever called double-numbers since it actually return normally from it. 如果没有call/cc我们会看到延续将是所谓的double-numbers因为它实际上从它返回。 Here is an example without call/cc : 这是一个没有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

I imagine it gets harder pretty quick. 我想它变得越来越难。 Eg. 例如。 my generator implementation . 我的发电机实施 The generator relies on having access to continuations to mix the generator code with where it's used, but without call/cc you'll need to do CPS in both the generator, the generated generator and the code that uses it. 生成器依赖于访问continuation以将生成器代码与其使用位置混合,但是如果没有call/cc ,则需要在生成器,生成的生成器和使用它的代码中执行CPS。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM