
On Sun, 6 Jul 2008, Isaac Dupree wrote:
Henning Thielemann wrote:
If your code divides by zero, you still want any "finally" or "bracket" clauses to get called before the program terminates.
A program which divides by zero is broken and must be fixed. A program which divides by zero but cleans up a bit, is still broken and must be fixed. Cleaning up may make things better, but may also make things worse!
it can make things worse? (When cleanup is somehow significantly dependent on the buggy part of the code that led to the error? How often does that happen??)
An error is a programming error, often a stupid mistake where you wonder, how this could happen. How do you predict how evil your mistakes are and whether the assumptions you put into the cleanup routines are fulfilled?
I appreciate how bugs in Haskell are much better-behaved than many languages. For finally-clauses, they should be called equally whether there is a legitimate IO exception (if you believe in such a thing; they're even in Haskell98 in a form), or a buggy-program exception, and there is no good reason to fail to call 'hClose' just because some pure code in some part of the program divided by zero.
As I answered to David, the file might well be deleted in the meantime. Your code is buggy and then it may well be that the file is already deleted. Maybe due to other "error handling code" that tried to recover from the division by zero.
This way, if my IO uses 'bracket' when it should, a bug in one part of the code is less likely to cause obscure bugs in entirely unrelated IO parts of the code. Exceptions are designed to be ubiquitous and always-possible...
That's especially unsatisfying. If you have to expect any exceptional situation at every time, you can no longer concentrate on what you intend to program. Instead, if I open a file I expect exceptions that are specific to that operation, and I handle them. I do not expecct OpenGL exceptions and not division by zero errors. With the design I proposed you can easily see in the type signature what exceptions can occur in an action (and btw. there is even no need to restrict this to IO, you can use this for any monad, e.g. monad transforms of IO).
especially when you consider asynchronous exceptions.
Do you mean the problem of 'readFile' raising an exception? I think we already clarified on Haskell-Cafe how to solve that properly: http://www.haskell.org/pipermail/haskell-cafe/2008-April/042050.html Instead of a signature like readFile :: IO (Either ErrorMsg String) the function should have a type like readFile :: IO (String, Maybe ErrorMsg) where the ErrorMsg is generated lazily when reading the file stops. If the file could be read completely it is Nothing, otherwise it is (Just errorMsg). The consumer of the file content can throw a regular exception after consuming the content. There is no need for complicating the exception handling mechanism.
In fact it's possible to use these exception capabilities to isolate different parts of the program from each other's bugs so the whole thing doesn't crash: although that's when it becomes much closer to your assessment of "a hack". That "hack" still can be quite useful, of course, if you agree with the Awkward Squad paper. It depends whether modularity of bugs is part of your worldview? -- I'm glad Linux (and all other modern OS) isolates different processes' address spaces using MMU!
I'm glad about all tools to help debugging - but please keep debugging and exception handling strictly separated! Let me give another example. We have learned that 'head' and 'tail' are evil, because they are undefined for some inputs. Using them we run into the risk of forgetting some cases. It is better to use a function like 'viewL' http://www.haskell.org/pipermail/haskell-cafe/2008-June/044179.html or to use 'case': case xs of [] -> a (_:ys) -> f ys Now, if we take the perspective that exceptions and errors are interchangeable, then we could also call 'tail' and catch the error in case the input list is empty. Do you consider this a good application of exception handling?