Proposal: Extensible exceptions

Henning Thielemann lemming at henning-thielemann.de
Mon Jul 7 06:20:06 EDT 2008


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?


More information about the Libraries mailing list