繁体   English   中英

理解调用的实现

[英]Understanding Implementation of call-with-continuation

我试图理解用 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

它来自本教程: http://norvig.com/lispy2.html

以上是如何工作的? ball是什么意思,为什么会用throw作为参数值来调用proc (edure?)? 评论“仅逃脱”是什么意思?


顺便说一句,这是我目前(可能是被误导的)对延续的理解,因为它适用于 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

[我不确定这个答案是否比另一个答案更有用:我在另一个答案之前开始了它,然后分心了。]

您真正希望能够用任何语言实现的事情是能够轻松地从某些上下文中逃脱回到给定点。 这显然是异常处理的基础,但它比这更普遍。 假设您有一些搜索程序:

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

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

有时你可以自然地表达这一点,这样当你找到你刚刚返回的东西时,它就会一直向上渗透。 有时这要困难得多。 所以你想要的是某种方式能够说“这是程序中的一个地方,这里有一台将返回那个地方的小机器”。 所以用一些假设的语言:

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

在这里,这个block构造建立了一个位置和从一个块return-from返回。

那么,如果你想要返回的块在词汇上对你来说是不可见的,你会怎么做? 您可以将return-from包装在 function 中:

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

这足以完成这个“逃到给定点”的事情:如果你在块的动态范围内调用这个过程,它会立即从块中返回它的参数。 请注意,它不会以某种方式搜索调用堆栈以寻找正确的返回位置:它只是直接进入块并从中返回一个值。

好吧,一个更自然的方法可能就是取消所有这些制作块的东西,而 go 直接到过程的事情:只需有一个过程将过程作为参数并调用它我在上面做的这个逃生程序。 这就是call/cc是什么:

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

现在,如果my-search-function或任何 function 它调用调用escape ,那么它将立即从call/cc表单返回其参数。

Python 没有真正像这样的构造(免责声明:我可能错了,因为我正在用更有趣的东西替换三年前我知道的 Python)。 在 Python 中return总是从词汇上最里面的 function return :你不能说return-from function 外部返回nonlocal的。 但是您可以使用异常来模拟它,因为异常具有标识。 因此,如果您发生异常,则可以将其包装在 function 中,这只会引发传递到您的代码中的异常。 调用此 function 只会引发该异常(不是相同的 class 之一:那个实际对象),在其中存储一个值。 然后你建立一个try... except:块,它检查它刚刚捕获的异常是否是刚刚创建的异常,如果它是相同的 object,它会返回它知道存储在那里的值。 如果不是,它只是重新加注。

所以这是一个 hack,因为如果你有很多嵌套的东西,很多处理程序会查看它并拒绝它,直到它找到它所属的那个。 但为此目的,这是一个可以接受的黑客攻击。 特别是这意味着您可以将 function 传递给另一个 function ,如果它调用它,它将从您创建它的位置返回一个值并放弃任何中间计算。

这个习语就像 GOTO 的一种非常结构化的用法:您可以进行非本地的控制转移,但只能在 function 调用链中的“上方”一点(众所周知,调用堆栈总是向下增长:这是因为建造在受拉下稳定的结构比在受压下稳定的结构要容易得多,并且结构故障也不会损坏故障上方的堆栈部分)。

这正是 Python 示例代码所做的:

  1. 它创建了一个异常, ball
  2. 它创建了一个过程throw将一个值存储在ball中,然后将其提升;
  3. 然后它以这个throw过程作为它的参数调用proc ,(在它返回的情况下返回对proc的调用的值),包裹在一个小小的try: ... except: ...块中,它检查这个特定的向上通过它的异常,如果它发现它返回值throw隐藏在其中。

因此,您可以使用它,例如,像这样:

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

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

这里search_with_escape实现了一些精细的搜索过程,可以通过调用escape来放弃。


但当然,这只是在 Scheme 中让你做的事情的一半。 因为一旦你得到了这个程序 object ,它将从某个地方返回,那么,它就是一个程序:它是一个一流的 object ,你可以返回它,然后如果你愿意,可以稍后调用。 在我们假设的语言中,这应该做什么:

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

好吧,在我们假设的语言(如您所见,它是 Lisp-2)中,这是一个运行时错误,因为当控制通过block形式传递出去时, return-from变得无效,所以虽然我有这个程序它不再有任何用处。

但这很可怕,对吧? 我怎么知道我不能调用这个东西? 我需要一些特殊的“可以在这里调用它”谓词吗? 为什么它不能做正确的事? 好吧,Scheme 的人正在感受他们的燕麦,他们做到了,因此 Scheme 等效项确实有效:

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

好吧,当我说“确实有效”时,它仍然是运行时错误,但原因完全不同:可以调用我称之为“转义过程”的东西,它会尽职尽责地从生成它的表单中返回一个值,无论在哪里。 所以:

  1. (call/cc (lambda (cc) cc))只返回延续 object;
  2. (let ((c...))...)将其绑定到c
  3. (c 3)调用延续...
  4. ... 从call/cc返回(再次) 3 ,其中...
  5. ... 将c绑定到 3;
  6. 现在您尝试调用(c 3)这是一个错误。

您需要将这些运行时错误变成如下内容:

(let ((c (call/cc (lambda (cc) cc))))
  (c (lambda (x) 3)))
  1. (call/cc...)像以前一样返回一个延续 object;
  2. (let... ...)将其绑定到c
  3. (c (lambda (x) 3)调用延续...
  4. ...从call/cc返回(lambda (x) 3) ,其中...
  5. ... 将c绑定到(lambda (x) 3)
  6. 现在你调用((lambda (x) 3) (lambda (x) 3)) ,它返回3

最后

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

我不打算解释。

你明白什么是延续吗? callcc(proc)表示使用称为“延续”的单个参数调用 function proc 如果稍后在代码中的某个地方,您使用参数调用此延续,它将返回调用延续的任何值返回给调用callcc的任何人。

throw是那个延续。 当您使用参数调用延续时,它会引发异常,然后弹出堆栈,直到找到对创建它的callcc的精确调用。 然后返回一个值。

一个真正的callcc实现,其实可以做很多这个实现做不到的事情。 延续超过堆栈。 但这是一个好的开始。

其他问题更正确,但我在 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

并运行它:

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