2008-08-29 49 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更加“泛化”,并且要求这样的构造也应该能够被程序员指定。