简体   繁体   English

理解调用的实现

[英]Understanding Implementation of call-with-continuation

I'm trying to understand a scheme procedure written in python code:我试图理解用 python 代码编写的方案过程:

def callcc(proc):
    "Call proc with current continuation; escape only"
    ball = RuntimeWarning("Sorry, can't continue this continuation any longer.")
    def throw(retval): ball.retval = retval; raise ball
    try:
        return proc(throw)
    except RuntimeWarning as w:
        if w is ball: return ball.retval
        else: raise w

It is from this tutorial: http://norvig.com/lispy2.html .它来自本教程: http://norvig.com/lispy2.html

How does the above work?以上是如何工作的? What does ball mean, and why would a proc (edure?) be called with a throw as its argument value? ball是什么意思,为什么会用throw作为参数值来调用proc (edure?)? And what does the comment "escape only" mean?评论“仅逃脱”是什么意思?


By he way, here is my current (probably misguided) understanding of continuation as it applies to python, which is similar to passing a function with a yield:顺便说一句,这是我目前(可能是被误导的)对延续的理解,因为它适用于 python,这类似于通过 function 的产量:

def c(func, *args, **kwargs):
    # func must be a coroutine
    return func(*args, **kwargs)

def inc(x=0):
    while True:
        yield x
        x += 1

>>> ct=c(inc, 3)
>>> next(ct)
3
>>> next(ct)
4

[I'm not sure if this answer is more useful than the other one: I started it before the other one and then got distracted.] [我不确定这个答案是否比另一个答案更有用:我在另一个答案之前开始了它,然后分心了。]

The thing you really want to be able to achieve in any language is the ability to painlessly escape from some context back up to a given point.您真正希望能够用任何语言实现的事情是能够轻松地从某些上下文中逃脱回到给定点。 This is obviously something which underlies exception-handling, but it's much more general than that.这显然是异常处理的基础,但它比这更普遍。 let's say you've got some search procedure:假设您有一些搜索程序:

(define (search-thing thing)
  (if (thing-is-interesting? thing)
      <return from search routine>
      (search-children (thing-children thing)))

(define (search-children children)
  ... (search-thing ...) ...)

Sometimes you can naturally express this so that when you've found the thing you just return and it percolates all the way up.有时你可以自然地表达这一点,这样当你找到你刚刚返回的东西时,它就会一直向上渗透。 Sometimes that's much harder.有时这要困难得多。 So what you'd like is some way of being able to say 'here is a place in the program and here is a little machine which will return to that place'.所以你想要的是某种方式能够说“这是程序中的一个地方,这里有一台将返回那个地方的小机器”。 So in some hypothetical language:所以用一些假设的语言:

(block here
  ...
  (return-from here ...)
  ...)

Here this block construct establishes a location and return-from returns from a block.在这里,这个block构造建立了一个位置和从一个块return-from返回。

Well, what do you do if the block you want to return from isn't lexically visible to you?那么,如果你想要返回的块在词汇上对你来说是不可见的,你会怎么做? You can wrap the return-from in a function:您可以将return-from包装在 function 中:

(block here
  ...
  (my-search-function (lambda (v) (return-from here v)) ...
  ...)

And this is enough to do this 'escape to a given point' thing: if you call this procedure within the dynamic extent of the block, it will return its argument from the block, immediately.这足以完成这个“逃到给定点”的事情:如果你在块的动态范围内调用这个过程,它会立即从块中返回它的参数。 Note that what it doesn't do is somehow search up the call stack looking for the right place to return from: it just goes directly to the block and returns a value from it.请注意,它不会以某种方式搜索调用堆栈以寻找正确的返回位置:它只是直接进入块并从中返回一个值。

Well, a more natural way to do this, perhaps, would just be to do away with all this making-a-block thing and go straight to the procedure thing: just have a procedure which takes a procedure as an argument and calls it with this escape-procedure I made above.好吧,一个更自然的方法可能就是取消所有这些制作块的东西,而 go 直接到过程的事情:只需有一个过程将过程作为参数并调用它我在上面做的这个逃生程序。 That's what call/cc is:这就是call/cc是什么:

(call/cc (lambda (escape)
           (my-search-function escape ...))

Now if my-search-function or any function it calls calls escape then it will immediately return its argument from the call/cc form.现在,如果my-search-function或任何 function 它调用调用escape ,那么它将立即从call/cc表单返回其参数。

Python has no construct really like this (disclaimer: I may be wrong about this as I am in the process of replacing the Python I knew three years ago with more interesting things). Python 没有真正像这样的构造(免责声明:我可能错了,因为我正在用更有趣的东西替换三年前我知道的 Python)。 return in Python always returns from the lexically innermost function: you can't say return-from to return from a function outside the lexically innermost one (there is nothing like nonlocal for return s).在 Python 中return总是从词汇上最里面的 function return :你不能说return-from function 外部返回nonlocal的。 But you can simulate it using exceptions, because exceptions have identity.但是您可以使用异常来模拟它,因为异常具有标识。 So if you make an exception then you can wrap it in a function which just raises that exception which gets passed into your code.因此,如果您发生异常,则可以将其包装在 function 中,这只会引发传递到您的代码中的异常。 Calling this function will just raise that exception (not one of the same class: that actual object), stashing a value in it.调用此 function 只会引发该异常(不是相同的 class 之一:那个实际对象),在其中存储一个值。 Then you establish a try... except: block which checks if the exception it's just caught is the one just created, and if it is the same object, it returns the value it knows is stashed there.然后你建立一个try... except:块,它检查它刚刚捕获的异常是否是刚刚创建的异常,如果它是相同的 object,它会返回它知道存储在那里的值。 If it's not it just reraises it.如果不是,它只是重新加注。

So this is a hack because if you have lots of these things nested lots of handlers get to look at it and reject it until it finds the one it belongs to.所以这是一个 hack,因为如果你有很多嵌套的东西,很多处理程序会查看它并拒绝它,直到它找到它所属的那个。 But it's an acceptable hack for this purpose.但为此目的,这是一个可以接受的黑客攻击。 In particular it means that you can pass a function into another function which, if it calls it, will return a value from where you created it and abandon any intermediate computation.特别是这意味着您可以将 function 传递给另一个 function ,如果它调用它,它将从您创建它的位置返回一个值并放弃任何中间计算。

This idiom like a very structured use of GOTO: you are allowed to do a nonlocal transfer of control, but only to a point 'above' you in the chain of function calls (as is well known call stacks always grow downwards: this is because it's much easier to build structures which are stable under tension than compression, and structural failures also don't damage the part of the stack above the failure).这个习语就像 GOTO 的一种非常结构化的用法:您可以进行非本地的控制转移,但只能在 function 调用链中的“上方”一点(众所周知,调用堆栈总是向下增长:这是因为建造在受拉下稳定的结构比在受压下稳定的结构要容易得多,并且结构故障也不会损坏故障上方的堆栈部分)。

And this is exactly what the Python sample code does:这正是 Python 示例代码所做的:

  1. it creates an exception, ball ;它创建了一个异常, ball
  2. it creates a procedure throw which stashes a value in ball and then raises it;它创建了一个过程throw将一个值存储在ball中,然后将其提升;
  3. it then calls proc with this throw procedure as its argument, (returning the value of the call to proc in the case that it does return), wrapped in a little try: ... except: ... block which checks for this specific exception passing upwards through it, and if it finds it returns the value throw stashed in it.然后它以这个throw过程作为它的参数调用proc ,(在它返回的情况下返回对proc的调用的值),包裹在一个小小的try: ... except: ...块中,它检查这个特定的向上通过它的异常,如果它发现它返回值throw隐藏在其中。

So you might use this, for instance, like this:因此,您可以使用它,例如,像这样:

def search(thing):
    callcc(lambda escape: search_with_escape(escape, thing))

def search_with_escape(escape, thing):
    ...
    if all_done_now:
        escape(result)
    ...

Here search_with_escape implements some elaborate search process, which can be abandoned by calling escape .这里search_with_escape实现了一些精细的搜索过程,可以通过调用escape来放弃。


But of course that's only half of what continuations let you do in Scheme.但当然,这只是在 Scheme 中让你做的事情的一半。 Because once you've got this procedure object which will return from somewhere, then, well, it's a procedure: it's a first-class object which you can return and then call later if you want.因为一旦你得到了这个程序 object ,它将从某个地方返回,那么,它就是一个程序:它是一个一流的 object ,你可以返回它,然后如果你愿意,可以稍后调用。 In our hypothetical language what should this do:在我们假设的语言中,这应该做什么:

(let ((c (block foo (lambda (v) (return-from foo v)))))
  (funcall foo 3))

Well, in our hypothetical language (which, as you can see, is a Lisp-2) that's a run-time error, because the moment control passes out through the block form the return-from becomes invalid, so although I have this procedure it's no longer any use.好吧,在我们假设的语言(如您所见,它是 Lisp-2)中,这是一个运行时错误,因为当控制通过block形式传递出去时, return-from变得无效,所以虽然我有这个程序它不再有任何用处。

But that's horrid, right?但这很可怕,对吧? How do I know I can't call this thing?我怎么知道我不能调用这个东西? Do I need some special 'it is OK to call this here' predicate?我需要一些特殊的“可以在这里调用它”谓词吗? Why can't it just do the right thing?为什么它不能做正确的事? Well, the Scheme people were feeling their oats and they made it so that the Scheme equivalent does work:好吧,Scheme 的人正在感受他们的燕麦,他们做到了,因此 Scheme 等效项确实有效:

(let ((c (call/cc (lambda (cc) cc))))
  (c 3))

Well, when I say 'does work' it's still a runtime error, but for a quite different reason: you are allowed to call the thing which I called an 'escape procedure' and it will dutifully return a value from the form that made it, wherever it is.好吧,当我说“确实有效”时,它仍然是运行时错误,但原因完全不同:可以调用我称之为“转义过程”的东西,它会尽职尽责地从生成它的表单中返回一个值,无论在哪里。 So:所以:

  1. (call/cc (lambda (cc) cc)) simply returns the continuation object; (call/cc (lambda (cc) cc))只返回延续 object;
  2. (let ((c...))...) binds it to c ; (let ((c...))...)将其绑定到c
  3. (c 3) invokes the continuation which... (c 3)调用延续...
  4. ... returns (again) 3 from call/cc , which... ... 从call/cc返回(再次) 3 ,其中...
  5. ... binds c to 3; ... 将c绑定到 3;
  6. and now you try to invoke (c 3) which is an error.现在您尝试调用(c 3)这是一个错误。

these runtime errors you need to make it into something like this:您需要将这些运行时错误变成如下内容:

(let ((c (call/cc (lambda (cc) cc))))
  (c (lambda (x) 3)))
  1. (call/cc...) returns a continuation object as before; (call/cc...)像以前一样返回一个延续 object;
  2. (let... ...) binds it to c ; (let... ...)将其绑定到c
  3. (c (lambda (x) 3) invokes the continuation which... (c (lambda (x) 3)调用延续...
  4. ... returns (lambda (x) 3) from call/cc , which... ...从call/cc返回(lambda (x) 3) ,其中...
  5. ... binds c to (lambda (x) 3) ; ... 将c绑定到(lambda (x) 3)
  6. and now you call ((lambda (x) 3) (lambda (x) 3)) which returns 3 .现在你调用((lambda (x) 3) (lambda (x) 3)) ,它返回3

And finally最后

(let ((c (call/cc (lambda (cc) cc))))
  (c c))

which I am not going to try to explain.我不打算解释。

Do you understand what a continuation is?你明白什么是延续吗? callcc(proc) says to call the function proc with a single argument called "the continuation". callcc(proc)表示使用称为“延续”的单个参数调用 function proc If somewhere later in your code, you call this continuation with an argument, it will return whatever value the continuation was called with back to whoever called callcc .如果稍后在代码中的某个地方,您使用参数调用此延续,它将返回调用延续的任何值返回给调用callcc的任何人。

throw is that continuation. throw是那个延续。 When you call the continuation with an argument, it raises an exception, and then pops the stack until it finds the precise call to callcc that created it.当您使用参数调用延续时,它会引发异常,然后弹出堆栈,直到找到对创建它的callcc的精确调用。 And then returns a value.然后返回一个值。

A real implementation of callcc can actually do a lot of things that this implementation can't do.一个真正的callcc实现,其实可以做很多这个实现做不到的事情。 The continuation outlives the stack.延续超过堆栈。 But this is a good start.但这是一个好的开始。

The other questions are more correct, but I'm posting a working example in python that can be used for testing:其他问题更正确,但我在 python 中发布了一个可用于测试的工作示例:

def callcc(function):
    bail = RuntimeWarning("My custom bail.")
    def escape_function(retval): 
        bail.retval = retval; # adding our functions return value into the exception itself
        raise bail
    try:
        # this will call the function and the escape function RAISES bail 
        # so it'll never return
        return function(escape_function)
    except RuntimeWarning as w:
        if w is bail: 
            retval = bail.retval
            print("About to return value of %s..." % retval)
            return retval
        else: 
            raise w

def countdown(n):
    # the function we are passing to callcc is `countdown_with_escape`
    # countdown_with_escape will later be called by callcc with the 'throw' as the escape function
    return callcc(lambda escape_function: countdown_with_escape(escape_function, n))


def countdown_with_escape(escape_function, n):
    while True:
        print (n)
        if n == 9:
            escape_function(n) # this passes '9' as the retval to the escape function
        n -= 1

And running it:并运行它:

x = countdown(20)
print ('Done with value: %s' % x)

20
19
18
17
16
15
14
13
12
11
10
9
About to return value of 9...
Done with value: 9

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

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