寻找延续的"真实"用法示例

时间:2020-03-05 18:44:54  来源:igfitidea点击:

我试图理解延续的概念,并且从Wikipedia的文章中找到了几个类似的小教学示例:

(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))

我知道这个小功能的作用,但是看不到它的任何明显用途。虽然我不希望很快在整个代码中使用延续,但我希望我知道在某些情况下适合使用延续。

因此,我正在寻找关于延续性可以为我提供程序员服务的更明确有用的代码示例。

干杯!

解决方案

回答

只要程序流程不是线性的,甚至不是预先确定的,都可以在"实际"示例中使用延续。 Web应用程序是一种常见的情况。

回答

某些Web服务器和Web框架使用连续性来存储会话信息。为每个会话创建一个连续对象,然后该会话中的每个请求都使用该对象。

这里有一篇关于这种方法的文章。

回答

海滨:

  • 主页
  • 维基百科页面

回答

@拍

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 ]

太赞了!

回答

在Algo&Data II中,我们一直使用这些时间从(长)函数中"退出"或者"返回"

例如,通过BFS算法遍历树的方式是这样实现的:

(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))

该函数还将给出一个"返回值":'node'

函数退出的原因是因为以下语句:

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

当我们使用exit时,它将返回到执行之前的状态,清空调用栈并返回我们赋予它的值。

因此,基本上,在这里使用call-cc来跳出递归函数,而不是等待整个递归本身结束(在执行大量计算工作时可能会非常昂贵)

另一个较小的示例对call-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))))

回答

我在http://www.randomhacks.net的这篇文章中使用延续来实现amb运算符的实现。

这是amb操作符的作用:

# 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

回答

我建立了自己的单元测试软件。在执行测试之前,我先存储延续,然后再执行测试,然后在失败时,我(可选)告诉方案解释器进入调试模式,然后重新调用延续。这样,我可以非常轻松地逐步解决有问题的代码。

如果延续是可序列化的,则还可以在应用程序失败时进行存储,然后重新调用它们以获取有关变量值,堆栈跟踪等的详细信息。

回答

对于服务器编程(包括Web应用程序前端)中的按请求线程,连续性是一个很好的选择。

在此模型中,我们不必在每次请求进入时都启动新的(重载)线程,而只是在函数中开始一些工作。然后,当我们准备阻止I / O(即从数据库读取)时,我们可以将延续传递到网络响应处理程序中。当响应返回时,我们将继续执行。使用这种方案,我们可以只用几个线程来处理许多请求。

与使用阻塞线程相比,这使控制流更加复杂,但是在繁重的负载下,它的效率更高(至少在当今的硬件上)。

回答

Google Mapplets API呢?有很多函数(都以"异步"结尾)传递给回调函数。 API函数执行一个异步请求,获取它的结果,然后将该结果传递给回调(作为"下一步要做的事情")。听起来很像延续传递给我的风格。

此示例显示了一个非常简单的情况。

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,因此没有尾部调用优化功能,因此堆栈会随着每次调用的延续而增长,最终我们将把控制线程返回给浏览器。都一样,我认为这是一个很好的抽象。

回答

连续性可用于实现异常(调试器)。

回答

如果必须调用异步操作并中止执行直到获得结果,通常可以轮询结果,或者将其余代码放入回调中,以在完成后由异步操作执行。使用连续性,我们不需要做低效的轮询选项,也不需要包装所有代码以在回调中的asynch事件之后运行,我们只需将代码的当前状态作为回调和上架操作完成后,该代码将被有效地"唤醒"。

回答

amb运算符是一个很好的示例,它允许类似prolog的声明式编程。

当我们说话时,我正在使用Scheme编写一个音乐创作软件(我是一个音乐家,几乎不了解音乐背后的理论,我只是在分析自己的作品以了解其背后的数学原理。)

使用amb运算符,我可以填写旋律必须满足的约束,然后让Scheme找出结果。

延续性可能会因为语言哲学而进入Scheme,Scheme是一个框架,使我们可以通过在Scheme本身中定义库来实现以其他语言找到的任何编程范例。延续是为了构成自己的抽象控制结构(例如"返回","中断")或者启用声明性编程。 Scheme更加"概括",并恳求程序员也应指定此类构造。