Re: [Scheme-reports] Comments on draft 6 about call/cc Andy Wingo (22 Feb 2012 17:20 UTC)
|
Re: [Scheme-reports] Comments on draft 6 about call/cc
Ray Dillinger
(22 Feb 2012 17:57 UTC)
|
A forwarded message from Oleg. *** From: oleg@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. _______________________________________________ Scheme-reports mailing list Scheme-reports@scheme-reports.org http://lists.scheme-reports.org/cgi-bin/mailman/listinfo/scheme-reports