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