
On Mon, 7 Jul 2008, Johan Tibell wrote:
On Sun, Jul 6, 2008 at 10:13 PM, Henning Thielemann
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/JavasCheckedExceptionsWe... 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.ht...
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.