The following example involves jumping into continuation and exiting out. Can somebody explain the flow of the function. I am moving in a circle around continuation, and do not know the entry and exit points of the function.
(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
Thanks.
The procedure iterates over a list, multiplying non-zero members and returning a result each time a zero is found. Resume-visit
stores the continuation for processing the rest of the list, and return-result
has the continuation of the call-site of the iterator. In the beginning, resume-visit
is defined to process the entire list. Each time a zero is found, a continuation is captured, which when invoked executes (process-list (rest lst) 1)
for whatever value lst
had at the time. When the list is exhausted, resume-visit
is set to a dummy procedure. Moreover, every time the program calls iter
, it executes the following:
(call/cc
(lambda (k)
(set! return-result k)
(resume-visit 'dummy)))
That is, it captures the continuation of the caller, invoking it returns a value to the caller. The continuation is stored and the program jumps to process the rest of the list. When the procedure calls resume-visit
, the loop is entered, when return-result
is called, the loop is exited.
If we want to examine process-list
in more detail, let's assume the list is non-empty. Tho procedure employs basic recursion, accumulating a result until a zero is found. At that point, p
is the accumulated value and lst
is the list containing the zero. When we have a construction like (begin (call/cc (lambda (k) first)) rest)
, we first execute first
expressions with k
bound to a continuation. It is a procedure that when invoked, executes rest
expressions. In this case, that continuation is stored and another continuation is invoked, which returns the accumulated result p
to the caller of iter
. That continuation will be invoked the next time iter
is called, then the loop continues with the rest of the list. That is the point with the continuations, everything else is basic recursion.
What you need to keep in mind is that, a call to (call/cc f)
will apply the function f
passed as argument to call/cc
to the current continuation. If that continuation is called with some argument a
inside the function f
, the execution will go to the corresponding call to call/cc
, and the argument a
will be returned as the return value of that call/cc
.
Your program stores the continuation of "calling call/cc
in iter
" in the variable return-result
, and begins processing the list. It multiplies the first 3 non-zero elements of the list before encountering the first 0. When it sees the 0, the continuation "processing the list element 0" is stored in resume-visit
, and the value p
is returned to the continuation return-result
by calling (return-result p)
. This call will make the execution go back to the call/cc
in iter
, and that call/cc
returns the passed value of p
. So you see the first output 6.
The rest calls to iter
are similar and will make the execution go back and forth between such two continuations. Manual analysis may be a little brain-twisting, you have to know what the execution context is when a continuation is restored.
You could achieve the same this way:
(define (prod-iter lst) (fold * 1 (remove zero? lst)))
... even though it could perform better by traversing only once.
For continuations, recall (pun intended) that all call/cc does is wait for "k" to be applied this way:
(call/cc (lambda (k) (k 'return-value)))
=> return-value
The trick here is that you can let call/cc return its own continuation so that it can be applied elsewhere after call/cc has returned like this:
;; 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
This lets a continuation return more than once by saving it in a variable. Continuations simply return the value they are applied to.
Closures are functions that carry their environment variables along with them before arguments get bounded to them. They are ordinary lambdas.
Continuation-passing style is a way to pass closures as arguments to be applied later. We say that these closure arguments are continuations. Here's half of the current code from my sudoku generator/solver as an example demonstrating how continuation-passing style can simplify your algorithms:
#| 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))
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.