2008-08-29 48 views
21

我試圖抓住延續的概念,我發現了幾個小的教學實例這樣一個從Wikipedia article尋找的「真實」的例子使用延續

(define the-continuation #f) 

(define (test) 
    (let ((i 0)) 
    ; call/cc calls its first function argument, passing 
    ; a continuation variable representing this point in 
    ; the program as the argument to that function. 
    ; 
    ; In this case, the function argument assigns that 
    ; continuation to the variable the-continuation. 
    ; 
    (call/cc (lambda (k) (set! the-continuation k))) 
    ; 
    ; The next time the-continuation is called, we start here. 
    (set! i (+ i 1)) 
    i)) 

我明白這個小功能確實,但我看不到它的任何明顯的應用。雖然我不希望在很短的時間內使用全部代碼,但我希望我知道一些可以適用的情況。

所以我正在尋找更明確地有用的代碼示例什麼延續可以爲我提供一個程序員。

乾杯!

回答

15

在算法中&數據II,我們用這些來「退出」或從(長)函數

例如「迴歸」的BFS algorthm穿越樹木是這樣實現的所有時間:

(define (BFS graph root-discovered node-discovered edge-discovered edge-bumped . nodes) 
    (define visited (make-vector (graph.order graph) #f)) 
    (define q (queue.new)) 
    (define exit()) 
    (define (BFS-tree node) 
    (if (node-discovered node) 
     (exit node)) 
    (graph.map-edges 
    graph 
    node 
    (lambda (node2) 
     (cond ((not (vector-ref visited node2)) 
       (when (edge-discovered node node2) 
       (vector-set! visited node2 #t) 
       (queue.enqueue! q node2))) 
      (else 
       (edge-bumped node node2))))) 
    (if (not (queue.empty? q)) 
     (BFS-tree (queue.serve! q)))) 

    (call-with-current-continuation 
    (lambda (my-future) 
    (set! exit my-future) 
    (cond ((null? nodes) 
      (graph.map-nodes 
      graph 
      (lambda (node) 
       (when (not (vector-ref visited node)) 
       (vector-set! visited node #t) 
       (root-discovered node) 
       (BFS-tree node))))) 
      (else 
      (let loop-nodes 
       ((node-list (car nodes))) 
       (vector-set! visited (car node-list) #t) 
       (root-discovered (car node-list)) 
       (BFS-tree (car node-list)) 
       (if (not (null? (cdr node-list))) 
       (loop-nodes (cdr node-list))))))))) 

正如你所看到的算法,當節點發現的函數返回true,將退出:

(if (node-discovered node) 
     (exit node)) 

功能也將給予「回報值「:‘節點’

爲什麼函數退出,是因爲這句話的:

(call-with-current-continuation 
     (lambda (my-future) 
     (set! exit my-future) 

當我們使用退出,它會回到狀態執行之前,清空調用堆棧和返回你給它的價值。

所以基本上,撥打-CC使用(在這裏)跳出一個遞歸函數,而不是等待整個遞歸本身結束

(做大量的計算工作時,它可以是相當昂貴)

另一個較小的例子做與呼叫-CC相同的:

(define (connected? g node1 node2) 
    (define visited (make-vector (graph.order g) #f)) 
    (define return()) 
    (define (connected-rec x y) 
    (if (eq? x y) 
     (return #t)) 
    (vector-set! visited x #t) 
    (graph.map-edges g 
        x 
        (lambda (t) 
         (if (not (vector-ref visited t)) 
         (connected-rec t y))))) 
    (call-with-current-continuation 
    (lambda (future) 
    (set! return future) 
    (connected-rec node1 node2) 
    (return #f)))) 
3

只要程序流程不是線性的,或甚至沒有預先確定,連續性就可以用於「現實生活」示例。熟悉的情況是web applications

+0

是的!我正在尋找代碼示例。 – 2008-09-19 20:51:54

9
+1

是的,Seaside就是一個很好的例子,我在6個月的項目中使用它!我正在尋找代碼示例。 – 2008-09-19 15:46:51

7

@Pat

海邊

是的,Seaside就是一個很好的例子。我很快瀏覽了它的代碼,發現這個消息展示了在Web上以一種看似完整的方式在組件之間傳遞控制權。

WAComponent >> call: aComponent 
    "Pass control from the receiver to aComponent. The receiver will be 
    temporarily replaced with aComponent. Code can return from here later 
    on by sending #answer: to aComponent." 

    ^AnswerContinuation currentDo: [ :cc | 
     self show: aComponent onAnswer: cc. 
     WARenderNotification raiseSignal ] 

太好了!

5

我來到翻過了amb運營商的this post實現從http://www.randomhacks.net,使用延續。

這裏的amb opeerator做什麼:

# amb will (appear to) choose values 
# for x and y that prevent future 
# trouble. 
x = amb 1, 2, 3 
y = amb 4, 5, 6 

# Ooops! If x*y isn't 8, amb would 
# get angry. You wouldn't like 
# amb when it's angry. 
amb if x*y != 8 

# Sure enough, x is 2 and y is 4. 
puts x, y 

下面是這篇文章的實現:

# A list of places we can "rewind" to 
# if we encounter amb with no 
# arguments. 
$backtrack_points = [] 

# Rewind to our most recent backtrack 
# point. 
def backtrack 
    if $backtrack_points.empty? 
    raise "Can't backtrack" 
    else 
    $backtrack_points.pop.call 
    end 
end 

# Recursive implementation of the 
# amb operator. 
def amb *choices 
    # Fail if we have no arguments. 
    backtrack if choices.empty? 
    callcc {|cc| 
    # cc contains the "current 
    # continuation". When called, 
    # it will make the program 
    # rewind to the end of this block. 
    $backtrack_points.push cc 

    # Return our first argument. 
    return choices[0] 
    } 

    # We only get here if we backtrack 
    # using the stored value of cc, 
    # above. We call amb recursively 
    # with the arguments we didn't use. 
    amb *choices[1...choices.length] 
end 

# Backtracking beyond a call to cut 
# is strictly forbidden. 
def cut 
    $backtrack_points = [] 
end 

我喜歡amb

6

我建立了我自己的單元測試軟件。在執行測試之前,我在執行測試之前存儲延續,然後在失敗時,我(可選)告訴方案解釋器進入調試模式,並重新調用延續。通過這種方式,我可以輕鬆地完成有問題的代碼。

如果延續是序列化的,你也可以再存儲在應用程序故障,然後再調用它們,以獲取有關變量值的詳細信息,堆棧跟蹤等

3

延續是於線程一個很好的選擇(包括web應用程序前端)在這個模型中,每次請求進入時,你不需要啓動一個新的(重)線程,而只需要在一個函數中開始一些工作。準備阻塞I/O(即從數據庫中讀取數據),你將延續傳遞給網絡響應處理程序,當響應返回時,執行延續。你可以用幾個線程處理大量的請求。

這使得控制流程比使用阻塞線程更復雜,但是在重負載下,它更有效率(至少在當今的硬件上)。

1

Google Mapplets API怎麼樣?有一堆函數(所有結尾都在Async之內),你傳遞一個回調函數。 API函數執行異步請求,獲取結果,然後將該結果傳遞給回調函數(作爲「接下來要做的事情」)。聽起來很像我continuation passing style

這個example顯示了一個非常簡單的情況。

map.getZoomAsync(function(zoom) { 
    alert("Current zoom level is " + zoom); // this is the continuation 
}); 
alert("This might happen before or after you see the zoom level message"); 

由於這是使用Javascript沒有tail call optimization,所以堆棧將每個呼叫到一個持續成長,你會控制線程,最終返回給瀏覽器。儘管如此,我認爲這是一個很好的抽象。

0

繼續可以用來實現異常,一個調試器。

1

如果必須調用異步操作並暫停執行,直到獲得結果爲止,那麼通常要麼輪詢結果,要麼將剩餘的代碼放入回調中,以便在完成時由異步操作執行。通過延續,您不需要執行輪詢的低效選項,並且您無需在回調中的異步事件之後包裝所有要運行的代碼 - 只需將代碼的當前狀態作爲回調函數傳遞即可 - 只要異步操作完成,代碼就會有效地「喚醒」。

2

amb運算符是一個很好的例子,它允許prolog-like聲明式編程。

正如我們所說,我正在爲Scheme編寫一段音樂作曲軟件(我是一個音樂家,幾乎不瞭解音樂背後的理論,我只是分析我自己的作品,看看背後的數學它的工作原理。)

使用amb運算符我可以填充旋律必須滿足的約束條件並讓Scheme計算出結果。

延續很可能投入計劃,因爲語言哲學的,方案是讓你實現有關方案本身定義庫在其他語言中找到任何編程範式的框架。延續是爲了構建自己的抽象控制結構,如「返回」,「中斷」或啓用聲明性編程。 Scheme更加「泛化」,並且要求這樣的構造也應該能夠被程序員指定。