[Scheme-reports] Comments on draft 6 about call/cc

Andy Wingo wingo at pobox.com
Wed Feb 22 12:19:43 EST 2012


A forwarded message from Oleg.

***
From: oleg at okmij.org
Date: 22 Feb 2012 11:01:57 -0000
Re: [Scheme-reports] Fwd: Comments on draft 6 about call/cc

 It would be difficult to do this without a suitably powerful
 replacement.  Imagine the headlines: "Scheme backtracks on call/cc" ;-)

Relegating to optional status won't probably change much in the short
term. Most Scheme systems will still support call/cc for the time
being. In the meanwhile, we can discuss powerful alternatives, which
do exists.

As to removing call/cc, I recall a talk at the Continuation Fest 2008
by a prominent Ruby developer, who was quite upset at call/cc 
added to Ruby without thinking through all the consequences. His
mildest proposal was to rename call/cc to something much longer than
call-with-current-continuation.

 [*] To implement a delimited call/cc on top of prompt and abort, you
 would need an additional operator that captures a partial continuation,
 but without unwinding the prompt. 

Not at all: see the definition of the `delimited' call/cc (called
cwcc) in terms of shift in Scheme48 distribution:

http://www.s48.org/cgi-bin/hgwebdir.cgi/s48-stable/file/c975d6f20901/scheme/misc/shift-reset.scm

(which has been there since about 1994).


 Why generators, when delimited continuations can implement them
 trivially?

Generators are a wide term, some versions of generators are equivalent
(equi-expressible) to shift/reset. Roshan James and Amr Sabry have
written a paper about all kinds of generators, arguing for the
generator interface.
	http://www.cs.indiana.edu/~sabry/papers/yield.pdf
(and other papers and talks on generators on Amr Sabry's page).

There is one notable advantage of generator interface is simplifying
the definition of dynamic-wind.

I have dug my old code showing that with shift-reset, dynamic-wind can
be implemented as a regular user function. Currently dynamic-wind is
one of the most complex functions (whose formal semantics is not
given, neither in R5RS nor R7RS). With shift-reset, dynamic-wind
becomes ordinary user-defined function that doesn't even have to be 
in a library. If we use ordinary shift/reset, the user has to provide
the functions cont-captured? and cont-replace. With the generator
interface, these extra functions are not needed and dynamic-wind winds
and unwinds generically.

	I'm enclosing the old code with a bit of explanations.

Fortunately, with delimited continuations, dynamic-wind is not needed
as a primitive and so requires no support the system.  If needed, the
user can encode dynamic-wind by himself. For example, suppose we wish
to capture a continuation that contains a file opening operation. We
wish the file to be closed when the continuation is captured, and
re-opened when the continuation is reinvoked. That is, we wish to
emulate the following Scheme code

       (define (foo)
	(let ((port #f))
	  (dynamic-wind
	    (lambda ()
		(display "opening the port") (newline)
		(set! port (open-input-file "/etc/motd")))
	    (lambda () 
		((call-with-current-continuation
		  (lambda (k)
			(lambda () (list k (lambda () port) (read port)))))))
	    (lambda () 
		(display "closing the port") (newline)
		(close-input-port port) (set! port #f)))))

	(let ((kr (foo)))
	  (display kr) (newline)
	  (if (and (pair? kr) (procedure? (car kr)))
	    ((car kr) (lambda () (read ((cadr kr)))))))

The above code prints the following:

opening the port
closing the port
(#<system continuation> #<procedure> freebsd)
opening the port
closing the port
freebsd


With shift-reset, we can code our own dynamic-wind below. It works
like the usual dynamic-wind, only with two additional arguments. The
first one, cont-captured?, is a procedure that determines if the thunk
has attempted to capture the continuation, and if so, return it.
The second procedure should replace the continuation part of thunk's
result with the new continuation.

(define (my-dyn-wind cont-captured? cont-replace
	  before-thunk thunk after-thunk)
 (let loop ((th (lambda () (reset (thunk)))))
    (before-thunk)
    (let ((res (th)))
      (after-thunk)
      (let ((captured-k (cont-captured? res)))
	(if captured-k
	   (let ((reenter (shift k (cont-replace k res))))
	     (loop (lambda () (captured-k reenter))))
	   res)))))

	   
     (define (bar)
	(let ((port #f))
	  (my-dyn-wind
	    (lambda (res) (and (pair? res) (procedure? (car res)) (car res)))
	    (lambda (knew res) (cons knew (cdr res)))
	    (lambda ()
		(display "opening the port") (newline)
		(set! port (open-input-file "/etc/motd")))
	    (lambda () 
		((shift k (list k (lambda () port) (read port)))))
	    (lambda () 
		(display "closing the port") (newline)
		(close-input-port port) (set! port #f)))))


(let ((kr (reset (bar))))
  (display kr) (newline)
  (let ((krnew ((car kr) (lambda () (read ((cadr kr)))))))
     (display krnew) (newline)))

The printed result is the same as that of the first example.

The definition of bar is almost the same as that of foo. However,
using (bar) is more intuitive than using (foo): the invocation of
(bar) does return once, as if bar were a regular function, which it
is. In contrast, the invocation of (foo) in the call/cc example is
weird: (foo) has returned twice.



More information about the Scheme-reports mailing list