On Sun 01 Dec 2019 21:41, Chris Vine <vine35792...@gmail.com> writes:
> Is this rewrite, and the new with-exception-handler procedure, an > opportunity to think about standardization of guile's implementation of > the R6RS/R7RS 'guard' form, or at least think about what is wanted for > 'guard'? > > The formal semantics (including specimen implementation) of 'guard' for > R6RS with the corrigendum to §7.1 of the standard library at > http://www.r6rs.org/r6rs-errata.html, and for R7RS without corrigendum > (at §4.2.7 and §7.3, page 72 of the standard), is: > > (i) to evaluate the guard body within a block with its own continuation > (as constructed by call/cc); > > (ii) if an exception is thrown, evaluate the handler (and its cond > clauses) in the dynamic context of the original caller of 'guard' via > that continuation; > > (iii) if no matching cond clause and no else clause is found, return to > the dynamic environment of the original 'raise' and re-raise the > exception with 'raise-continuable', even for non-continuable > exceptions. > > If a fully conforming R6RS/R7RS implementation runs this code: > > (guard (exn [(equal? exn 5) #f]) > (guard (exn [(equal? exn 6) 'never-reached]) > (dynamic-wind > (lambda () (display "in") (newline)) > (lambda () (raise 5)) > (lambda () (display "out") (newline))))) > > the code evaluates to #f and should print this: > > in > out > in > out > > In chez scheme it does so. In most other implementations (including > guile and racket) it seems to print: > > in > out I really think the standards messed up regarding the specification of "guard": http://scheme-reports.org/mail/scheme-reports/msg03247.html But those ships have sailed and are now lost at sea. Guile currently has two separate implementations of "guard" for SRFI-34 (used by R7RS) and R6RS. It would seem that besides not respecting the specification, the R6RS one is broken, as it expects the "cond" clauses to evaluate to a single value. For SRFI-34 (and R7RS), after the exception refactor, I did a re-write to give a shot at implementing the specified behavior. It works with a caveat: because it uses delimited continuations as the rewind mechanism, and Guile has a limitation that some delimited continuations can't be rewound (if the continuation bounces through C), then re-raising the exception fails because the context can't be rewound. This can cause previously working programs to break! Which makes me think, if call/cc (rather than call-with-prompt / abort-to-prompt) is necessary to implement "guard", we are in a bad place and we should specify something else. I have long thought that the right thing to do is this: we evaluate the "cond" tests in the dynamic environment of the "raise". Then if a test succeeds, we unwind and run the corresponding consequent. That way there's no rewinding. Here's an implementation: (define-syntax guard (syntax-rules (else) ((guard (var (t e e* ...) ...) body body* ...) (let ((tag (make-prompt-tag))) (call-with-prompt tag (lambda () (with-exception-handler (lambda (var) (cond (t (abort-to-prompt tag (lambda () e e* ...))) ...) (raise var)) (lambda () body body* ...))) (lambda (k thunk) (thunk))))))) Though I think it might be reasonable to use "raise-continuable" instead of "raise" if nothing matches. WDYT? Andy