【问题标题】:scheme continuations -need explanation方案延续 - 需要解释
【发布时间】:2011-12-13 22:51:41
【问题描述】:

以下示例涉及跳入延续和退出。有人可以解释一下函数的流程吗?我绕着continuation转了一圈,不知道函数的入口点和出口点。

(define (prod-iterator lst)
  (letrec ((return-result empty)
        (resume-visit (lambda (dummy) (process-list lst 1)))
        (process-list
         (lambda (lst p)
           (if (empty? lst)
               (begin
                 (set! resume-visit (lambda (dummy) 0))
                 (return-result p))
               (if (= 0 (first lst))
                   (begin
                     (call/cc ; Want to continue here after delivering result     
                      (lambda (k)
                        (set! resume-visit k)
                        (return-result p)))
                     (process-list (rest lst) 1))
                   (process-list (rest lst) (* p (first lst))))))))
    (lambda ()
      (call/cc
       (lambda (k)
         (set! return-result k)
         (resume-visit 'dummy))))))

(define iter (prod-iterator '(1 2 3 0 4 5 6 0 7 0 0 8 9)))
(iter) ; 6                                                                        
(iter) ; 120                                                                      
(iter) ; 7                                                                        
(iter) ; 1                                                                        
(iter) ; 72                                                                       
(iter) ; 0                                                                        
(iter) ; 0

谢谢。

【问题讨论】:

    标签: scheme continuations


    【解决方案1】:

    该过程遍历一个列表,将非零成员相乘并在每次找到零时返回一个结果。 Resume-visit 存储用于处理列表其余部分的延续,return-result 具有迭代器调用站点的延续。一开始,resume-visit 被定义为处理整个列表。每次找到一个零时,就会捕获一个延续,当调用它时,无论lst 当时的值如何,都会执行(process-list (rest lst) 1)。当列表用完时,resume-visit 被设置为一个虚拟过程。而且,程序每次调用iter,都会执行以下操作:

    (call/cc
       (lambda (k)
         (set! return-result k)
         (resume-visit 'dummy)))
    

    也就是说,它捕获调用者的延续,调用它返回一个值给调用者。延续被存储并且程序跳转以处理列表的其余部分。 过程调用resume-visit时进入循环,调用return-result时退出循环。

    如果我们想更详细地检查process-list,让我们假设列表非空。 Tho 过程采用基本递归,累积结果直到找到零。此时,p 是累加值,lst 是包含零的列表。当我们有一个像(begin (call/cc (lambda (k) first)) rest) 这样的结构时,我们首先执行first 表达式,k 绑定到一个延续。这是一个在调用时执行rest 表达式的过程。在这种情况下,存储该延续并调用另一个延续,它将累积结果p 返回给iter 的调用者。下次调用 iter 时将调用该延续,然后循环继续列表的其余部分。这就是延续的重点,其他一切都是基本递归。

    【讨论】:

    • 感谢您的回答,但仍不清楚。你能详细解释一步一步吗?
    • 这比上一个好多了。看了几遍就明白了。现在,我正在尝试实现它。顺便说一句,非常感谢。它有很大帮助。再次感谢。
    【解决方案2】:

    您需要记住的是,对(call/cc f) 的调用会将作为参数传递给call/cc 的函数f 应用于当前延续。如果在函数f 中使用某个参数a 调用该延续,则执行将转到对call/cc 的相应调用,并且参数a 将作为该call/cc 的返回值返回。

    您的程序将“在iter 中调用call/cc”的延续存储在变量return-result 中,并开始处理该列表。它在遇到第一个 0 之前将列表的前 3 个非零元素相乘。当它看到 0 时,继续“处理列表元素 0”存储在 resume-visit 中,并将值 p 返回到通过调用(return-result p) 继续return-result。这个调用将使执行回到iter中的call/cc,并且call/cc返回p的传递值。所以你会看到第一个输出 6。

    iter 的其余调用是相似的,并且会在这两个延续之间来回执行。手动分析可能有点脑筋急转弯,你得知道一个continuation恢复的时候执行上下文是什么。

    【讨论】:

      【解决方案3】:

      你可以通过这种方式达到同样的效果:

      (define (prod-iter lst) (fold * 1 (remove zero? lst)))
      

      ...即使只遍历一次就可以表现得更好。

      对于延续,回忆(双关语)所有 call/cc 所做的就是等待以这种方式应用“k”:

      (call/cc (lambda (k) (k 'return-value)))
        => return-value
      

      这里的诀窍是你可以让 call/cc 返回它自己的延续,这样它就可以在 call/cc 像这样返回后应用到其他地方:

      ;; returns twice; once to get bound to k, the other to return blah
      (let ([k (call/cc (lambda (k) k))]) ;; k gets bound to a continuation
        (k 'blah)) ;; k returns here
        => blah
      

      这可以通过将延续保存在变量中来多次返回。延续只是返回它们所应用的值。

      闭包是在参数绑定到它们之前携带环境变量的函数。它们是普通的 lambda。

      继续传递样式是一种将闭包作为参数传递以供稍后应用的方法。我们说这些闭包参数是延续。以下是我的数独生成器/求解器中当前代码的一半作为示例,展示了延续传递样式如何简化您的算法:

      #| the grid is internally represented as a vector of 81 numbers
      example: (make-vector 81 0)
      
      this builds a list of indexes |#
      (define (cell n) (list (+ (* (car 9) (cadr n))))
      (define (row n) (iota 9 (* n 9)))
      (define (column n) (iota 9 n 9))
      (define (region n)
        (let* ([end (+ (* (floor-quotient n 3) 27)
                       (* (remainder n 3) 3))]
               [i (+ end 21)])
          (do ([i i
                (- i (if (zero? (remainder i 3)) 7 1))]
               [ls '() (cons (vector-ref *grid* i) ls)])
            ((= i end) ls))))
      
      #| f is the continuation
      
      usage examples:
      (grid-ref *grid* row 0)
      (grid-set! *grid* region 7) |#
      (define (grid-ref g f n)
        (map (lambda (i) (vector-ref g i)) (f n)))
      (define (grid-set! g f n ls)
        (for-each (lambda (i x) (vector-set! g i x))
          (f n) ls))
      

      【讨论】:

      • 这是一个具体示例,其中延续传递样式是传递命名闭包以应用于主体的最佳方式:
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-02-21
      • 1970-01-01
      • 2016-03-31
      • 1970-01-01
      • 1970-01-01
      • 2023-01-26
      • 2012-11-03
      相关资源
      最近更新 更多