anton0xf
anton0xf

Reputation: 49

How to use tagged prompts with call/cc in Racket?

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?

Upvotes: 1

Views: 386

Answers (1)

Ryan Culpepper
Ryan Culpepper

Reputation: 10653

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.

Upvotes: 3

Related Questions