[Haskell-cafe] Exceptions

Alastair Reid alastair at reid-consulting-uk.ltd.uk
Fri Oct 1 12:51:47 EDT 2004


On Friday 01 October 2004 16:43, John Goerzen wrote:
> One of the most important features of a modern language is, to me,
> exceptions.  I have found very little coverage of exceptions in Haskell,
> what what coverage there was seemed to be centered on I/O.

There's three things one might call exceptions in Haskell:

1) IOErrors result from IO operations and can be caught in the IO monad.

2) Exceptions result from pure or IO code and can be caught in the IO monad.
   They are raised by calling raise (in pure code) or raiseIO (in IO code).
   They are also raised by pattern match failure, calls to error and 
    things like division by zero.
   They are non-deterministic in the following sense:

     The expression '(raise exn1) + (raise exn2) :: Int' may raise
     either exception 'exn1' or exception 'exn2' depending on 
     choice of compiler, optimization options, environment settings,
     current time of day, phase of moon, etc.

   It is because of this non-determinism that exceptions can _only_
   be caught in the IO monad.
   Another consequence is that exceptions are not very useful for
    reporting errors in user input where determinism, reporting
    errors before starting a long computation, being able
    to report multiple errors, etc are useful.

3) Error values can be distinguished using the Maybe or Either types
   and/or using monads.  For example, parsing command line arguments
   might yield:

     Right 42

   or

     Left "Error: can't parse '42.0' - integer expected"

   The advantage of this approach is that the type system tracks where
   errors can propagate to so you can be confident that error checking
   is being performed _before_ spending 3 days computing a result and
   not when you go to write the answer.
   The disadvantage is that it is a bit tedious - but monads, lifting, etc.
   help a lot with this.

In the following, I'll tak exclusively about the 2nd type (since the other two 
are obvious).

> 1. Can exceptions be used in "pure" functions (outside of monads?)

They can be raised in pure functions but can only be caught in IO functions.
(If you simply want to change what exception is reported, that can be done in 
pure code.)

> 2. How are these exceptions caught and handled aside from using bracket?

There is a catch function with type:

  catch :: IO a -> (Exception -> IO a) -> IO a

Built on top of this, are functions to do things like try-finally.  One of my 
favourites is 'bracket' which takes an initial action, a final action and a 
middle action (in that order) and makes sure that the final action is 
performed even if the middle action fails.  For example:

  bracket (open "/etc/passwd") hClose $ \ h -> do
    cs <- hGetContents h
    mapM crack (lines cs)

The strange order of the arguments is to put the initial action (opening the 
file) next to the final action (closing the file) - which is often a good 
thing.

> 3. Can I define my own exception types?

Sadly, no.  There is only one 'exception type'.

You can use dynamics and encode new kinds of exceptions as Strings
 but that isn't very satisfactory.

> 4. Can I write code that can catch and handle multiple different
> exception types from a single block of code?

You use standard Haskell pattern matching so, yes, you can catch multiple 
kinds of exceptions.  They're not different types though.

> 5. Is there anything different about working with exceptions in monads?

Use raiseIO instead of raise to raise exceptions.

> 6. Can I get a stack trace from ghc or hugs if an exception is never
> caught and thus causes the program to terminate?

Sadly, no.

--
Alastair Reid


More information about the Haskell-Cafe mailing list