Proposal: Extensible exceptions

Henning Thielemann lemming at henning-thielemann.de
Wed Jul 9 10:17:25 EDT 2008


On Mon, 7 Jul 2008, Johan Tibell wrote:

> On Sun, Jul 6, 2008 at 10:13 PM, Henning Thielemann
> <lemming at henning-thielemann.de> wrote:
>>  In Modula-3 you have to add the exceptions that can be raised to a
>> PROCEDURE header. Java has adopted this mechanism.
>
> Many people argue that this was a mistake [1, 2, 3, 4].
>
> ...
>
> 1. http://radio.weblogs.com/0122027/stories/2003/04/01/JavasCheckedExceptionsWereAMistake.html
> 2. http://www.mindview.net/Etc/Discussions/CheckedExceptions
> 3. http://www.ibm.com/developerworks/java/library/j-jtp05254.html
> 4. http://www.oreillynet.com/onjava/blog/2004/09/avoiding_checked_exceptions.html

When reading those articles I get the impression, that people dislike 
Java's checked exceptions because of deficiencies in the system. They have 
seen so many times that programmers just catched exceptions but did not 
handle them. It's exactly the same like ignoring return values, which is 
so easy in C - and also in Haskell's do notation. I think we can avoid 
some of the deficiencies of checked exceptions in Haskell, and should not 
try to sweep the problems under the carpet.
  Those people seem to prefer unchecked exceptions now. I believe that this 
led to the confusion with respect to exceptions and errors. If there would 
only be checked exceptions and errors would be the same as exceptions, 
then one would have to write prototypes like
   void quickSort (...) throws IndexViolation;
  that is one had to expose possible bugs in the interface, which is 
certainly not wanted. :-) This way it might become clearer what I said 
before: Errors aka bugs are unexpected exceptional situations, whereas 
Exceptions are expected situations.


Let me recall how exceptions entered programming languages. In languages 
which have no exceptions there are only exceptional return values, say 
NULL instead of a pointer to a valid address, or a negative value where 
non-negative values are regular results. In other cases a routine returns 
a success/error code and the computation result is stored in a VAR or 
pointer parameter. This way programs look like

if (file = OpenFileRead("foo")) {
    if (window = OpenWindow(windowParams)) {
       if (button = CreateButton (window, buttonParams)) {
          ...
          DeleteButton (button);
       } else {
          printf ("button not created\n");
       }
       CloseWindow (window);
    } else {
       printf ("window not opened\n");
    }
    CloseFile (file);
} else {
    printf ("file could not be read\n");
}

Language designers found it unsatisfying that most of the structure of the 
program is dictated by rare cases, by the _exceptions_. Thus they 
developed a mechanism, which let you write many commands one after another 
and handling the exceptional cases once for a block of commands. Ideally 
this is combined with a mechanism that frees resources, that were already 
allocated in a block. Thus the exception mechanism was invented for 
handling rare but expected cases, it was not intended for debugging. A 
division by zero or an illegal memory access immediately stops a program 
in a language without exceptions. Trying to cleanup, to show a bug report 
form or to save user data in case of an error, can be a good thing, but 
should not be mixed up with handling of exceptions like "file not found". 
There is also another unintended application: Escaping from loops or deep 
recursions, maybe even returning a result by an exception. Actually, in 
order to define the interaction of abort mechanisms like RETURN in a 
PROCEDURE or EXIT in LOOP, the Modula-3 designers defined RETURN and EXIT 
in terms of exceptions (although they suggested to implement them more 
efficiently). These abuses of exceptions made Niklaus Wirth, the inventor 
of Pascal, fear, that exceptions bring back a GOTO into structured 
programming languages.

In Haskell we have more luck: We can combine exceptional and regular 
values in a safe way, with Maybe and Either (a type specific to exceptions 
would be better of course), and we can also return multiple values easily. 
Thus we can express exceptional situations more easily and safely. We also 
have overloadable Monad combinators, which allow us to forget the 
exceptions in a block of consecutive operations. Control.Monad.Error shows 
the way.

In Modula-3 we have a problem with exceptions in call-back functions. A 
function f, which has the function g as parameter, should certainly f 
raise all exceptions that g can raise and additionally the exceptions that 
it raises itself. In Modula-3 you cannot express that, but in Haskell you 
can:

f :: (a -> ErrorT exc IO b) -> a -> ErrorT (Either FException exc) IO b

Now it may be that the exceptions in 'exc' overlap with those from 
FException. This might be resolved by a type class, which merges exc with 
FException or by some more type hackery, if you like.

  In the end, people prefering "unchecked exceptions" can still use
   (ErrorT Dynamic IO a)
  However the default should be an explicit description of the exceptional 
cases in the type signature, just as in the Error monad.
  I have just tried to add support for newer Oracle versions in HSQL's 
Oracle back-end which uses throwDyn. I wanted to catch the exception, that 
a system specific table cannot be found, in order to search for another 
one. However finding out, which exception to catch, means reading the 
implementation of the table opening function, rather then reading its type 
signature. This reminds me on programming in dynamically typed languages. 
I think we can do much better in Haskell.


More information about the Libraries mailing list