计划延续 - 需要解释

时间:2011-12-13 22:51:42

标签: scheme continuations

以下示例涉及跳转到延续和退出。有人可以解释功能的流程。我围绕延续移动了一圈,并且不知道函数的入口和出口点。

(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

感谢。

3 个答案:

答案 0 :(得分:4)

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

iter

也就是说,它捕获调用者的延续,调用它会向调用者返回一个值。存储继续,程序跳转以处理列表的其余部分。 当过程调用(call/cc (lambda (k) (set! return-result k) (resume-visit 'dummy))) 时,输入循环,当调用resume-visit时,循环退出。

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

答案 1 :(得分:1)

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

的值

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

iter的其余调用是相似的,并且会使执行在这两个延续之间来回传递。手动分析可能有点大脑扭曲,你必须知道恢复延续时的执行上下文。

答案 2 :(得分:1)

你可以这样做:

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

......即使只通过一次也可以表现得更好。

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

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

这里的诀窍是你可以让call / cc返回它自己的continuation,这样它就可以在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

通过将连续保存在变量中,可以使连续返回多次。 Continuations只返回它们应用的值。

闭包是在参数与它们绑定之前携带其环境变量的函数。他们是普通的羔羊。

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

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