At 10:28 Uhr -0500 04.01.2006, Marc Feeley wrote:
It is possible using undocumented procedures. Here's what you want:
Thank you.
I'm planning to change this so that the default user-interrupt- handler behaves like exceptions, i.e. the default will be to terminate the program after displaying "*** INTERRUPTED IN ...".
The default for unix programs is that SIGINT makes them terminate *and deliver that fact to the parent* (through wait(2) the parent can know if the child terminated because of a signal and which one it was). If you trap SIGINT, then afaik the only way to make the parent know we are terminating because of a signal is to remove our signal handler and then killing ourselves again. This poses the question as to why a signal handler has been installed in the first place. I think that printing a message and exiting with an error code is a nonstandard and thus suboptimal solution, and that not installing signal handlers at all unless really necessary would be, in principle, a better solution. (I'm mainly coming from perl, which works that way.) I don't want to suggest to give this a high priority though and also have never really programmed on non-unix systems.
With the runtime option "-:d" the system will instead start a REPL. With the runtime option "-:d0" the program will terminate silently. However, when inside a REPL (such as the one normally started in interactive mode), a SIGINT will start a nested REPL.
That makes sense to me.
(*) that leads to another question I've been wondering about: parameterize doesn't accept procedures which aren't parameters (it checks their type). This makes the above suggestion impossible to be implement transparently of course, since it would require running code other than just for setting the dynamic value (**).
I don't understand what you mean.
Here's a normal parameter p1, and a self-written parameter like function p2 which has the side effect of something like initializing signal setup if not done already:
(define p1 (make-parameter #f))
(define pseudo-signal-handler-hook #f) (define p2 (let ((absent (gensym)) (value #f)) (lambda (#!optional (newvalue absent)) (if (eq? newvalue absent) value (begin (if (not pseudo-signal-handler-hook) (set! pseudo-signal-handler-hook (lambda () (display "hello world\n")))) (set! value newvalue))))))
; > (p1) ; #f ; > (p1 "hello") ; > (p1) ; "hello" ; > (p2) ; #f ; > pseudo-signal-handler-hook ; #f ; > (p2 "world") ; > (p2) ; "world" ; > pseudo-signal-handler-hook ; #<procedure #2 pseudo-signal-handler-hook> ; > (#2) ; hello world
; > (begin (parameterize ((p1 "a")) (display (p1))(newline)) (display (p1))(newline)) ; a ; #f ; > (begin (parameterize ((p2 "a")) (display (p2))(newline)) (display (p2))(newline)) ; *** ERROR IN (console)@3.8 -- PARAMETER expected ; 1>
The problem is that my side-effecting pseudo parameter cannot be transparently used with parameterize. It occured to me as an unnecessary drawback when I first realized that parameters were procedures but procedures can't be parameters. I did assume parameterize were implemented about like this (e.g. querying the old value, storing in lexical context, setting new value with dynamic-wind):
; (let ((#:g10 (p2))) ; (dynamic-wind ; (lambda () (p2 "a")) ; (lambda () (display (p2)) (newline)) ; (lambda () (p2 #:g10))))
(let ((#:var2 "a")) (let ((#:swap4 (lambda () (let ((#:tmp3 (p2))) (p2 #:var2) (set! #:var2 #:tmp3))))) (dynamic-wind #:swap4 (lambda () (display (p2)) (newline)) #:swap4)))
; as generated by this macro:
(define-macro (my-parameterize bindings . body) (let* ((b-alist (map (lambda (l) (cons l (gensym 'var))) bindings)) (b->var (lambda (l) (cdr (assoc l b-alist)))) (b->name car) (b->init cadr) (tmp (gensym 'tmp)) (swap (gensym 'swap))) `(let ,(map (lambda (l) (list (b->var l) (b->init l))) bindings) (let ((,swap (lambda () ,@ (map (lambda (l) `(let ((,tmp (,(b->name l)))) (,(b->name l) ,(b->var l)) (set! ,(b->var l) ,tmp))) bindings)))) (dynamic-wind ,swap (lambda () ,@ body) ,swap)))))
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.
Which other reasons are there for implementing parameters differently? Performance or semantics? I guess the above implementation would be rather slow. I've taken a cursory look at the srfi-39 document and discussion archive, but most of it is over my head right now, I'll reread later.
So, it seems that the "most natural way" of how parameterize would be implemented is not the best and thus I shouldn't expect to be able to put callbacks into a supposedly lowlevel dynamic bindings mechanism.
What's the reason for this restriction?
Parameters are special procedures that contain a key. This key is used to lookup the binding of the parameter. The machinery is quite complex (see _thread.scm).
I'll reread it later.
((**) without this, the user would have to run some (user-interrupt- initialize!) hook first -- at least, that might be better for thread safety?)
Thanks, Christian.
Afficher les réponses par date
At 21:30 Uhr +0100 04.01.2006, Christian wrote:
The problem is that my side-effecting pseudo parameter cannot be transparently used with parameterize.
Well, I forgot that one can specify a filter function, with which the signal initialization example can be solved.
The case which I ran into a few months ago, where I was disappointed to find that parameterize doesn't let me use ordinary functions, was different; IIRC I wanted kind of a *read* filter instead, for generating values on the fly -- I can't remember exactly what it was.
BTW I would also like to point out that proper-tail-calls-set! would better be turned into a parameter, so one could do stuff like:
(define (load-no-tco file) (parameterize ((current-proper-tail-calls #f)) (load file)))
I've also not found a function to query the current setting, thus currently I'm doing this instead:
(define (load-no-tco file) (proper-tail-calls-set! #f) (load file) (proper-tail-calls-set! #t))
but this changes the previous setting if it has not been #t.
Christian.
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.
Christian, in general I agree with your analysis. This is a bug in with-exception-catcher and the REPL. I have now changed it so that the dynamic-wind stack is properly wound and unwound. This has required the addition of the procedures ##continuation-[graft|return]- with-winding.
I also uncovered a bug which caused the dynamic-environment (shown with the ,e command) to be wrong in higher frames, and a bug in dynamic-wind when re-entering the dynamic-wind.
However:
On 7-Jan-06, at 10:24 AM, Christian wrote:
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.
I don't understand. What is ugly? You know about the ,y REPL command. Do you mean you would like the system to implicitly execute this command when a new REPL is started? I'm not convinced this is useful because the error message contains all this information, but in a different (more novice friendly) format.
(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?
Changed.
Marc
At 2:30 Uhr -0500 08.01.2006, Marc Feeley wrote:
On 7-Jan-06, at 10:24 AM, Christian wrote:
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.
I don't understand. What is ugly? You know about the ,y REPL command. Do you mean you would like the system to implicitly execute this command when a new REPL is started? I'm not convinced this is useful because the error message contains all this information, but in a different (more novice friendly) format.
(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.)
What I wanted to say is only: if I use my safer-dynamic-wind implementation (which uses the trick to capture the result of running (old-handler e) [e.g. the repl] before returning from the handler), then if old-handler is the repl and an error is thrown, I'm put into the repl, but the first continuation being shown by the repl is the (let ((result ..)) ..) one, and not the one where (error ..) has been called. So if I wanted to work around the exception-catcher bug by using this safer-dynamic-wind code, one has to first enter ,+ to really see the place where the error happened.
Of course, that's now moot since you have fixed the bug.
Thanks Christian.