On Thu, 2012-03-22 at 17:35 +0100, Helmut Eller wrote:
> We know now that R6RS-style guard requires full continuations. I'm
> not
> sure what "termination-style behavior" exactly is but it sounds like
> something that should NOT use full continuations.
It allows for the handling of conditions outside of the influence of the
calling code, which is not provided for easily by any of the other
forms. While we could have chosen to remove this dependency on
continuations, let me address your notes.
> Therefore I think R6RS-style guard is flawed. guard could avoid full
> continuations in (at least) two ways:
I would respond that just because we can avoid full continuations is
not, IMO, a good reason to do so.
> 1. When no cond-clause matches, guard re-raises the condition in the
> dynamic context of the guard form. This is similar to Java's
> try-catch (+ the implicit re-throw).
It is possible to do this with the current guard form explicitly, but it
is not possible to go the other way easily.
> 2. Evaluate the cond-tests in the dynamic environment of the original
> raise. The advantage of this strategy is that it preserves the
> context of the error; which is useful for debugging. This is
> similar
> to Common Lisp's HANDLER-CASE.
This is already the semantics of normal raise or raise-continuable calls
using with-exception-handler, and thus, already provided for. Guards
should not generally result in any loss of error information, and
usually, a guard is the wrong thing to use if you intend to halt
computation, or if you want to handle continuable notifications that
should return to the normal flow of things. Guards are designed for
handling the cases where an error in the computation of the body
prevents further reasonable execution of that body, but which does not
necessarily stop the entire computation outside of the body. The guard
allows one to easily provide a result to the outer context in the cases
when it may be impossible to get a reasonable result from the body form.
This is more difficult to accomplish in a nice way using the existing
semantics of the other forms because you would have to explicitly
capture a continuation around the code and then use an exception handler
that called out to the continuation to get this same behavior. Even that
is not quite right because you do not necessarily want the dynamic
extent of the body to pollute your response in the case where the body
code is no longer helpful.
As an example, convincing or otherwise:
(define (notify-gui-maybe msg)
(define (alert) (display msg) (newline))
(guard (c [(error? c) (alert)])
(parameterize ([current-output-port gui-output-port])
(alert))))
Here, I am thinking of a custom output port that reports messages into a
gui window of some application. However, there may be a problem, where
perhaps the connection to the GUI is gone, or the window that the port
relied on was closed by the user. We want to do the guard outside so
that we can retry the (alert) using the normal output port, but if there
was another message that was signalled, such as a warning, then we
really want that to occur inside the the parameterize so that it can
take advantage of the gui message port. Thus, this back and forth
behavior is exactly what we want in this case. Moreover, using the guard
form, it is very clear what we are doing here, but if I were to write
the same thing explicitly with continuations and with-exception-handler,
things would get very hairy very quickly. User's are likely to end up
implementing this over and over again, because this pattern is common.
It is better to have a common exception handling paradigm that supports
this behavior from the start.
I hope that this at least clarifies some of the rationale for guard.
--
Aaron W. Hsu | arcfide@sacrideo.us | http://www.sacrideo.us
Programming is just another word for the lost art of thinking.