簡體   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