[gambit-list] safe dynamic-wind
Christian
christian at pflanze.mine.nu
Sat Jan 7 10:24:06 EST 2006
At 21:30 Uhr +0100 04.01.2006, Christian wrote:
>I know this implementation of parameterize has a problem though:
>dynamic-wind is not safe when using exceptions. Why? I find this
>especially strange considering that I thought with-exception-catcher
>was implemented using continuations.
Here's an example, taken and adapted from
http://sisc.sourceforge.net/manual-beta/ch03.html#ErrorHandling
but using the more lowlevel with-exception-handler instead of
with-exception-catcher:
(define (test capture call)
(let* ((x 0)
(result
(capture
(lambda(cont)
(with-exception-handler
(lambda(e)
(call cont e))
(lambda()
(dynamic-wind
(lambda () (set! x (+ x 1)))
(lambda () (/ 1 0))
(lambda () (set! x (+ x 1))))))))))
(values result x)))
; > (load "bug-dynamic-wind")
; "/home/chris/schemedevelopment/gambit/mod/maybe-bug-dynamic-wind.scm"
; > (test call/cc (lambda (cont e) (cont e)))
; #<divide-by-zero-exception #2>
; 2
; > (test ##continuation-capture (lambda (cont e)
(##continuation-return cont e)))
; #<divide-by-zero-exception #3>
; 1
; > (test ##continuation-capture (lambda (cont e)
(##continuation-graft cont (lambda() e))))
; #<divide-by-zero-exception #4>
; 1
So:
- if I implement my own catcher using R5RS call/cc, it works as I
expected and as SISC works.
- if I implement my own catcher using raw lowlevel continuations, it
does work (or "not work") like with-exception-catcher.
Do I understand correctly that the raw lowlevel continuation
mechanism is dynamic-scope unaware, and the implementation of
call-with-current-continuation (in _thread.scm, I don't currently
understand enough of it) has to take special steps for making
dynamic-wind work?
If so: why are those not used in the exception catcher
implementation, because of efficiency? BTW entering ,d into a nested
repl jumps to the enclosing repl with an unsafe jump as well.
I've tried to come up with a safe variant of dynamic-wind:
(define (safer-dynamic-wind before main after)
(let ((old-handler (current-exception-handler)))
(with-exception-handler
(lambda (e)
;; install old-handler to prevent infinite loops
;; from exceptions in after thunk
(current-exception-handler old-handler)
;; (this shadows the original error though, which is bad too;
;; todo: combine errors into multi-exception objects, or output
;; a warning or something similar.)
(after)
(let ((result (old-handler e)))
(before)
result))
(lambda ()
(dynamic-wind
before
main
after)))))
; > (safer-dynamic-wind (lambda () (println "before")) (lambda () (+
(error "hello") 1)) (lambda () (println "after")))
; before
; after
; *** ERROR IN #<procedure #12>, "maybebug-dynamic-wind.scm"@134.22 -- hello
; 1> ,(c 100)
; before
; after
; 101
; > (safer-dynamic-wind (lambda () (println "before")) (lambda () (+
(error "hello") 1)) (lambda () (println "after")))
; before
; after
; *** ERROR IN #<procedure #13>, "maybebug-dynamic-wind.scm"@134.22 -- hello
; 1> #||#,+;
; 1 #<procedure #14> (stdin)@35.66 (error "hello")
; 1\1> ,(c 101)
; after
; 102
; >
So it helps as long as I don't short-cut away my continuation in the
repl. It's also rather ugly that I don't immediately see the position
of the code where the error happened when being thrown into the
debugger. (For inspecting the lexical environment of the place where
the debugger happens I'll also step one up, and then it's too easy to
forget to step back before continuing.)
At least for the repl, efficiency is no concern - could it be changed
to use safe continuations?
Christian.
More information about the Gambit-list
mailing list