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.