I am new to Scheme programming and have been trying to understand the control flow of the programs having call-with-current-continuation in them. To be more specific, I want to know when the call to any continuation is invoked where is the control transferred and what happens after that. It would be really helpful if the below mentioned program is considered for the explanation.
(define call/cc call-with-current-continuation)
(define amb-exit '())
(define amb
(lambda ()
(call/cc
(lambda (m)
(call/cc
(lambda (f1)
(set! amb-exit (lambda () (f1 'exit)))
(m 1)))
(call/cc
(lambda (f2)
(set! amb-exit (lambda () (f2 'exit)))
(m 2)))
(call/cc
(lambda (f3)
(set! amb-exit (lambda () (f3 'exit)))
(m 3)))))))
(define back (lambda () (amb-exit)))
Now, I try to run the code this way (define a (amb)) and then I get the value in the terminal like this ;Value: a. Then in the terminal I check the value of a which returns me ;Value: 1. Then I call (back) I get a with the new value ;Value: 2. So on...
I know that when I do (define a (amb) the continuation f1 is invoked in the statement (set! amb-exit (lambda () (f1 'exit))) which transfers the control back to the first inner call/cc and the f1 continuation returns exit.
The thing that I am not able to understand is why the ;Value: a is ;Value: 1 instead of the value exit which is returned by f1?
The moment this part (f1 'exit) is executed, control returns back to first inner call/cc abandoning whatever (in this case (m 1)) after it.
So, this part (m 1) should never be invoked because the first inner continuation i.e. f1 is returned with exit even before hitting (m 1).
Any helpful comments regarding call-with-current-continuation in Scheme would also be appreciated.
Note: Using MIT/GNU Scheme
No, when you do (define a (amb)) the continuation f1 is not invoked, because it is behind (i.e. inside) a lambda.
No, (set! amb-exit (lambda () (f1 'exit))) sets amb-exit to the lambda function, and then the control passes to (m 1) which does invoke the continuation m. Which returns 1 from (amb) in (define a (amb)), which thus sets a to 1.
When you later call (back) it calls (amb-exit) which at that point invokes the f1 continuation with (f1 'exit) which returns the value 'exit from the (call/cc (lambda (f1) ...)) form. This value is discarded, and control passes into the (call/cc (lambda (f2) ...)) form, with similar effects.
Related
Why this code
(let ([cc #f]
[pr (make-continuation-prompt-tag 'pr)])
(call-with-continuation-prompt
(λ () (displayln
(+ 2 (call-with-current-continuation
(λ (k) (set! cc k) 1)
pr))))
pr)
(cc 4))
(on Racket v7.5) raise exception?:
3
; continuation application: no corresponding prompt in the current continuation
; Context:
; /usr/share/racket/collects/racket/repl.rkt:11:26
While same code with default tag works as expected:
(let ([cc #f])
(call-with-continuation-prompt
(λ ()
(displayln (+ 2 (call-with-current-continuation
(λ (k) (set! cc k) 1))))))
(cc 4))
3
6
And same (as the first snippet) code with composable continuation
(let ([cc #f]
[pr (make-continuation-prompt-tag 'pr)])
(call-with-continuation-prompt
(λ () (displayln
(+ 2 (call-with-composable-continuation
(λ (k) (set! cc k) 1)
pr))))
pr)
(cc 4))
works as expected too:
3
6
What is wrong with the first snippet? Or what is wrong with my understanding?
From the docs for call-with-current-continuation:
If the continuation argument to proc is ever applied, then it removes the portion of the current continuation up to the nearest prompt tagged by prompt-tag (not including the prompt; if no such prompt exists, the exn:fail:contract:continuation exception is raised), ....
In your first example, when you apply cc, there is no prompt for pr in the context ("on the stack") when the application occurs, so it raises an exception.
The second example works because there is always a prompt for the default tag.
The third example works because call-with-composable-continuation creates a continuation procedure that does not abort the current continuation, so there is no precondition for its application. (That's part of why it's considered a "composable" continuation.)
If it helps, here is approximately how call/cc can be defined in terms of the abort-current-continuation and call-with-compposable-continuation. (Warning: I haven't tested this, so there may be bugs.) In the type annotations below, I use the following conventions: P is the result type associated with a prompt tag and A is the result type of a call/cc or call/comp call, which is also the type of the continuation's argument. ⊥ is the empty type; it effectively means "doesn't return".
;; call-with-continuation-prompt : (-> P) PromptTag[P] -> P
;; Only allows default abort handler!
;; abort-current-continuation : PromptTag[P] (-> P) -> ⊥
;; Assumes the default abort handler!
;; call-with-composable-continuation : ((A -> P) -> A) PromptTag[P] -> A
;; call/cc : ((A -> ⊥) -> A) PromptTag[P] -> A
(define (call/cc proc tag)
(call-with-composable-continuation
(lambda (ck) ;; ck : A -> P
;; k : A -> ⊥
(define (k v)
(abort-current-continuation tag
(lambda () (ck v))))
(proc k))
tag))
This definition doesn't account for how call/cc actually interacts with dynamic-wind, it doesn't work with custom prompt handlers, and it doesn't account for multiple return values (which correspond to multiple continuation arguments), but it should give you a rough idea of what call/cc is doing. In particular, the call to abort-current-continuation requires that the current continuation has a prompt tagged with tag.
Does call/cc in Scheme the same thing with yield in Python and JavaScript?
I am not clear about generators. In my opinion, yield gives a language the ability to generate iterators without pain. But I am not sure whether I am right.
Does call/cc in Scheme has something related to yield in other languages? If so, are they the same thing, or what is the difference?
Thanks!
call/cc is a much more general language feature than generators. Thus you can make generators with call/cc, but you cannot make call/cc with generators.
If you have a program that computes values and use those values in other places its basically a step machine.. One might think of it as a program that have one function for each step and a continuation for the rest of the steps. Thus:
(+ (* 3 4) (* 5 6))
Can be interpreted as:
((lambda (k)
(k* 3 4 (lambda (v34)
(k* 5 6 (lambda (v56)
(k+ v34 v56 k)))))
halt)
The k-prefix just indicate that it's a CPS-version of the primitives. Thus they call the last argument as a function with the result. Notice also that the order of evaluation, which is not defined in Scheme, is in fact chosen in this rewrite. In this beautiful language call/cc is just this:
(define (kcall/cc kfn k)
(kfn (lambda (value ignored-continuation)
(k value))
k))
So when you do:
(+ (* 3 4) (call/cc (lambda (exit) (* 5 (exit 6)))))
; ==> 18
Under the hood this happens:
((lambda (k)
(k* 3 4 (lambda (v34)
(kcall/cc (lambda (exit k)
(exit 6 (lambda (v6)
(k* 5 v6 k)))
k))))
halt)
By using the substitution we can prove that this actually does exactly as intended. Since the exit function is invoked the original continuation is never called and thus the computation cancelled. In contrast to call/cc giving us this continuation that doesn't seem obvious it's no magic in CPS. Thus much of the magic of call/cc is in the compiler stage.
(define (make-generator procedure)
(define last-return values)
(define last-value #f)
(define (last-continuation _)
(let ((result (procedure yield)))
(last-return result)))
(define (yield value)
(call/cc (lambda (continuation)
(set! last-continuation continuation)
(set! last-value value)
(last-return value))))
(lambda args
(call/cc (lambda (return)
(set! last-return return)
(if (null? args)
(last-continuation last-value)
(apply last-continuation args))))))
(define test
(make-generator
(lambda (collect)
(collect 1)
(collect 5)
(collect 10)
#f)))
(test) ; ==> 1
(test) ; ==> 5
(test) ; ==> 10
(test) ; ==> #f (procedure finished)
One might make a macro to make the syntax more similar, but it's just sugar on top of this.
For more examples I love Matt Mights page with lots of examples on how to use continuations.
Here's code to define a generator:
(define-syntax define-generator
(lambda (x)
(syntax-case x (lambda)
((stx name (lambda formals e0 e1 ...))
(with-syntax ((yield (datum->syntax (syntax stx) 'yield)))
(syntax (define name
(lambda formals
(let ((resume #f) (return #f))
(define yield
(lambda args
(call-with-current-continuation
(lambda (cont)
(set! resume cont)
(apply return args)))))
(lambda ()
(call-with-current-continuation
(lambda (cont)
(set! return cont)
(cond (resume (resume))
(else (let () e0 e1 ...)
(error 'name "unexpected return"))))))))))))
((stx (name . formals) e0 e1 ...)
(syntax (stx name (lambda formals e0 e1 ...)))))))
There are examples of the use of generators at my blog. Generators use call-with-current-continuation, in a manner similar to yield in Python, but are much more general.
You can implement generators with call/cc. Here is an example:
coroutines.ss
They work in a similar way to python and ECMAScript generators.
This code works as expected:
(define saved #f)
(cons 'wo (call/cc (lambda (k) (set! saved k) '())))
(saved 'ca!)
output (Racket console):
'(wo)
'(wo . ca!)
But when I wrap it in a function and call it, the program never stops. Why?
(define (test)
(define saved #f)
(cons 'wo (call/cc (lambda (k) (set! saved k) '())))
(saved 'ca!))
(test)
A continuation is all that's left to be done in the execution context where it's saved.
In the first case, the continuation is saved when calling cons, so it's just to cons 'wo to something and return to the REPL.
In the second case, you call procedure test, so the continuation is both
cons 'wo to something
call the procedure bound to saved (i.e. the continuation) with 'ca!
so the continuation calls itself, hence the loop.
This code works as expected:
(define saved #f)
(cons 'wo (call/cc (lambda (k) (set! saved k) '())))
(saved 'ca!)
output (Racket console):
'(wo)
'(wo . ca!)
But when I wrap it in a function and call it, the program never stops. Why?
(define (test)
(define saved #f)
(cons 'wo (call/cc (lambda (k) (set! saved k) '())))
(saved 'ca!))
(test)
A continuation is all that's left to be done in the execution context where it's saved.
In the first case, the continuation is saved when calling cons, so it's just to cons 'wo to something and return to the REPL.
In the second case, you call procedure test, so the continuation is both
cons 'wo to something
call the procedure bound to saved (i.e. the continuation) with 'ca!
so the continuation calls itself, hence the loop.
I'm trying to implement a return function in Scheme R6RS. I want something such that:
(lambda ()
(do-some-job-before)
(return some-value)
(do-some-job-after))
executes (do-some-job-before), do not execute (do-some-job-after) and the final value of the lambda function in some-value.
I guess I have to use a continuation. I tried:
(define return #f)
(call/cc (lambda (k)
(set! return k)))
but it does not work; e.g
(+ 2 (return 3)) ; -> 3 (and not 5 as I expected)
How can i do this?
Edited: Misread question.
Very easy in fact :)
(call/cc
(lambda (return)
(printf "before\n")
(return 3)
(printf "after\n")))
Example here.
Note: You cannot generalize this, except if you wrap it in syntax from an unhygienic macro.