[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Proposal for exception handling



    Date: Tue, 5 Sep 1995 18:13 EDT
    From: Christopher Haynes <chaynes@cs.indiana.edu>

    The following is a proposal for exception handling in Scheme. [...] An
    HTML version of this proposal is in the Scheme repository as

      http://www.cs.indiana.edu/scheme-repository/doc.proposals.exceptions.html.

    ...

    (current-exception-handler [handler])                           procedure

    When invoked without arguments, the current exception handler is
    returned.  When invoked with a handler argument, handler is installed
    as the current exception handler and an unspecified value is returned.

The ability to set the active handler seems pretty random.  I see no reason this
shouldn't be always just bound.  I don't mind if as an implementation extension,
some version of Scheme provides a way to set the global one but the effect of doing
so depends so heavily on the characteristics of the system you're operating in that
I think you're kidding yourself if you think this is a portable way to affect the
global environment safely.  And as for affecting a local environment, I've never
encountered the need.

    with-continuing-handlers is like with-handlers, except that handlers
    return to the continuation of the handler call, rather than to the
    continuation of the with-continuing-handler expression.

I don't understand the need for this.  If the user wants this, and I don't
deny he might sometimes want it, I think call/cc is sufficient for
offering this expressive capability, which doesn't seem to be
motivated out of fundamental consumer demand.

    with-rec-handlers is like with-handlers, except that handlers are
    invoked in a dynamic context in which the current exception handler is
    the one established by the with-rec-handlers expression.

I believe there is some need for this concept, but not enough to warrant a
special operator in the language.  If I had to pick only a handful of
operators (and I confess to not understanding the drive that Scheme
people sometimes have to limit the number of builtin operators at the
price of complicating every possible user program), this would not be among
the few I would choose to go on the condition handling lifeboat.

==============================================================================

    Date: Thu, 14 Sep 1995 12:01:19 -0400
    From: kelsey@research.nj.nec.com
    
    I would like to offer a counter proposal [...]

    [General dig at the RRRS authors: the system-exception? procedure
    described below is required if programs are to be able distinguish
    between their and the system's exceptions.  If Scheme included some
    way for programs to define new opaque types it would not be needed.]
    
Scheme should definitely provide a way to define new opaque types.

The distinction between system-exceptions and other exceptions seems
contrived in any case.  Any reasonable proposal for condition handling
should define a way to make them such that there was no distinction.
What is, after all, "the system"?  Part of a continuum of layering that
begins at the hardware -> microcode -> ... -> "system" -> user layer 1 ->
user layer 2 -> ...  The idea that the "system" is dignified in some way
that makes it special seems to me to be just exposing an artificial 
weakness of the language that forces you to see implementation detail you
ought not.

If there is any value to going slowly in Scheme design (and I am often
skeptical that there ever is), it is that you don't add in things that are
mistakes.  Making a distinction between "system" and "non-system" is a
mistake.

Overall, of the two proposals, I like this proposal better.  But that's
not saying much.  It is missing important things.

==============================================================================

    Date: Thu, 14 Sep 1995 15:01:23 -0400
    From: "John D. Ramsdell" <ramsdell@linus.mitre.org>
    
    I find I must agree that Richard's proposal is more in keeping with
    the current Scheme Report.
    
Apparently, John, you and I are in agreement on this.  But I still have to say
I laughed when I saw this.  In my head, I said to myself:  Yeah, neither the
Scheme report nor this proposal provides enough meat to get serious work done.
"in keeping" indeed. :-)

I don't mean to demean the value of being "like" the other stuff.  But I
just wish "spartan" were not the only thing Scheme sometimes seemed to offer.

==============================================================================

				Interlude

Condition systems are NOT about control structure.

Control structure is already present in Scheme and other languages that
desperately need control structure.  Adding syntactic sugar does not cut it.

Condition systems ARE about implementing a handshake protocol between unrelated
parts of programs which have partial information needed to compose a solution
to a problem.  Those unrelated parts are these:

 - The fact that a problem has occurred.   ("signal" or "raise")

   The process of signalling is like an introduction service between 
   points error signalers and error handlers.

 - The willingness to offer a point of recovery.  ("restarts")

   Enough introspective capability must be offered such that a program
   ("handler") can inspect the useful options and choose a correct one without
   knowing in advance that it is definitely there or not.

   Further, there cannot be a uniquely determined notion of "continuing".
   Protocols must allow for multiple possible ways to recover from the same error.
   Sometimes there is an active symmetry of two choices.  Sometimes there are just
   various nested layers of things, only one of which you might want to return to
   on any given occasion.

 - The willingness to offer help in deciding. ("handlers")

   The very essence of signalling is saying "Something has happened.  I do not 
   understand what to do now."  Yet the fact of a restart to supply a permanent
   value to a variable that is unbound is not proof that taking that restart is
   right.  The fact of pills in your medicine cabinet is not proof that you should
   take them any time you ail.  Some smart entity should help you decide among the
   choices.

Each of these three points should be possible to contribute modularly by programs.
In order to do this, one additional piece of information is needed:

 A way of representing a problem situation in a common manner such that any of the 
 distributed components can figure out what is going on.  The manner cannot be
 specific to the application with risk of confusion.  For example, if you use
 lists like ("Unbound variable." FRED) as a problem description, then you cannot
 return them from functions that might return condition descriptions or "real" 
 results because there is a representational confusion between them.  A condition
 needs to be its own type.  Further, a condition needs certain services upon it
 that allow you to poke at it and ask questions of various kinds that can give you
 partial information.

 For example: "Are you a condition?" or "Gee, I don't know what it means when
 you say you're a MISSING-CLOSE-PAREN-IN-ALGOL-FUNCTION-CALL-EXPRESSION condition
 is.  Are you a kind of PARSE-ERROR or STREAM-ERROR?" or "Please print yourself
 to the following stream in a form that a person can understand" or "Please customize
 your presentation for a French speaker."

 Sure, you can invent custom protocols for doing these things and not put them in
 the language, but then you lose the ability for a program that doesn't know about
 another program to ever accidentally be able to help it.  Most of condition handling
 is about felicity that comes from modular information provided in a distributed way
 using a common representational structure to combine.  As long as you use opaque
 predicates and insist on no specification of a common structure, you lose a LOT.

==============================================================================

    Date: Thu, 14 Sep 95 15:54:50 -0400
    From: Stephen Adams <adams@martigny.ai.mit.edu>
    
    Two exception handling mechanisms have been proposed. [...]
    Both miss the point of having a standardised exception system.
    An exception system is useful only if it is integrated with 
    the rest of the language.

    If the proposal specifies some standard exceptions, and for
    each place in R4RS that currently says `an error is
    signalled' specifies what exception is raised in order to
    signal that error, then the proposal would be useful.
    
This would certainly help.  I don't think it's all that's needed.  But it
forces the designers to directly confront the issue of "what do people
need to know" and I think that's for the good.

    I think it is also important to organise the exceptions into
    some kind of standardized and extensible system [...]

Right.
    
    Unless a proposal addresses the issues of how the system exceptions
    are organized and which exceptions correspond to which errors or
    conditions, it is not particularly useful for writing portable robust
    programs. [...]

Well, here I'll part company ever so slightly.

When I did the condition system for Common Lisp, I asked a lot of
questions about whether people wanted X or Y subtle feature and they all
gave me blank stares.  Ultimately, the decision to go ahead was not based
upon a belief that the arbitrary choices I'd made in many cases was right,
but a belief that there was no way to get from here to there without going
through rough times.  I believed (and believe still that I was right) that
no one would have strong opinions about conditions until they'd had to
live with some set of choices--any set of choices--for a while.

I think that if you put SOMETHING in the language, even if it's wrong,
then you will develop a community of users who will tell you it's junk and
will know very precisely where and why.  And then maybe you'll have a basis
for dialog.

Right now, you have a community of people who know they need an error
system and think this is enough.  I think they are wrong.  I see no way to
prove that other than to add one of these doodly little half-systems and
then see if the gripes about "needing an error system" go away. I think
they will not go away, but at least then we will not be having a dialog
about whether this or that minimal system is what's needed and we can move
on to a serious discussion of what omissions were "too much".

As such, even though they're missing things I consider to be fundamental,
I'd rather see either of these proposals than nothing.  Though there are
things I'd rather see than either of these.

==============================================================================

			    Not-Quite-Proposal

Since I *have* been through it more than once now and since I *do* have an
idea of what I want, here's a list of what I'd like to see in a condition
system.  This is very rough and is not a formal proposal.  It also omits
most of the useful syntax I'd want to add (other than ignore-errors) and
focuses on primitive functionality.  I don't have time to spend doing a
full proposal, but I hope someone will at least ponder whether someone
else should find the time.  I'd be happy to review any such proposal.

 (make-condition kind ...data...)

   Some mechanism for making an object of an opaque type that is possible
   to (a) recognize as a condition, (b) inspect data within, and (c)
   perform other services common to all conditions.

 (condition? condition)               tests for a condition.
 (signal-condition condition)         invokes the signalling process.
 (report-condition condition stream)  presents the condition in natural
				      language to the given stream.

 (call-with-handler handler-thunk thunk)

    Establishes handler-thunk as the dynamically active handler and calls thunk.
    If handler is invoked, it MUST transfer control.  If it doesn't know what to
    do, it should CALL-NEXT-HANDLER.

 (call-next-handler [condition])

    Abandons execution of a handler and moves to next handler.  If
    condition is supplied, moves to next handler in signalling process for
    condition (useful for nested signals).

 (call-with-restart restart-tag restart-thunk condition thunk [report-thunk [interactive-thunk]])

    Invokes thunk in such a way that if condition is signalled, the restart-thunk
    will be visible as a restart.  (If #f is supplied in place of a
    condition, the restart is available for all conditions.)

 (find-restart restart-tag [condition])

    Finds any restart for restart-tag.  If condition is given, considers only those
    which are associated with condition or are not associated with any condition.
    If no condition is given, all restarts are considered.

 (invoke-restart restart ...data...)

    Calls the indicated restart with appropriate data.

 (restart? restart)        tests for a restart

 (report-restart restart)  presents the restart (using report-thunk).

 (invoke-restart-interactively restart)  calls restart, prompting interactively
     for data using interactive-thunk.

 Here are some possible error classes (the ones needed for ISLISP, the dialect of
 Lisp which ISO working group WG16 is designing):

 Add concrete classes of errors:

                                        <condition>
                                       /         
                          <serious-condition>
                         /                   \
      <storage-exhausted>                     <error>
                                                 |
    ------+------------+-------------------+-----+--+-----------------
   /      |            |                   |        |                 \
   |  <control-error>  |                   |    <stream-error> <simple-error>
   |                   |                   |        |
 <parse-error>         |                   |    <end-of-stream>
                <program-error>            |
               /       |                   |
 <domain-error>  <undefined-variable>      |
                                           |
                                <arithmetic-error>
                               /        |         \
                              /         |          \
    <floating-point-underflow>  <division-by-zero>  <floating-point-overflow>

Data needed by some of these conditions:

    class                 data
    -----                 ----

    <simple-error>        format-string, format-arguments
    <parse-error>         string, expected-class
    <domain-error>        object, expected-class
    <undefined-variable>  name
    <arithmetic-error>    operation, operands
    <stream-error>        stream

Predefined restart tags:

 abort,   data args: ()   - exits task, returning to command level or exiting program
			    all user programs should be executed in a context where
			    this is available.
 continue, data args: ()  - continues from simple errors that have an "obvious" 
			    continuation
 use-value, data args: (value) - used in unbound variable errors, for example, to
				 supply a value to use once (but not store)
 store-value, data args: (value) - used in unbound variable errors, for example, to
				   supply a value to use and store for future use.

Common utility functions/macros:

 (error msg . args)  ;function
    Equivalent to
     (signal-condition
        (create <simple-error> 'format-string msg 'format-arguments (list . args)))

 (ignore-errors . forms) ;macro

    which establishes a handler for
    <error> that returns nil from ignore-errors.