The Exception library provides an interface for raising and catching both built-in and user defined exceptions.
Exceptions are defined by the following (non-abstract) datatype:
data Exception = IOException IOError -- IO exceptions (from 'fail') | ArithException ArithException -- Arithmetic exceptions | ErrorCall String -- Calls to 'error' | NoMethodError String -- A non-existent method was invoked | PatternMatchFail String -- A pattern match failed | NonExhaustiveGuards String -- A guard match failed | RecSelError String -- Selecting a non-existent field | RecConError String -- Field missing in record construction | RecUpdError String -- Record doesn't contain updated field | AssertionFailed String -- Assertions | DynException Dynamic -- Dynamic exceptions | AsyncException AsyncException -- Externally generated errors | NonTermination -- Infinite Loop instance Eq Exception instance Ord Exception instance Show Exception data ArithException = Overflow | Underflow | LossOfPrecision | DivideByZero | Denormal instance Eq ArithError instance Ord ArithError instance Show ArithError data AsyncException = StackOverflow | HeapOverflow | ThreadKilled instance Eq AsyncException instance Ord AsyncException instance Show AsyncException
An implementation should raise the appropriate exception when one of the above conditions arises. Note: GHC currently doesn't generate the arithmetic or the async exceptions.
Exceptions may be thrown explicitly from anywhere:
throw :: Exception -> a
There are several functions for catching and examining exceptions; all of them may only be used from within the IO monad. Firstly the try family of functions:
tryAll :: a -> IO (Either Exception a) tryAllIO :: IO a -> IO (Either Exception a) try :: (Exception -> Maybe b) -> a -> IO (Either b a) tryIO :: (Exception -> Maybe b) -> IO a -> IO (Either b a)
The simplest version is tryAll. It takes a single argument, evaluates it (as if you'd applied seq to it), and returns either Right a if the evaluation succeeded with result a, or Left e if an exception was raised, where e is the exception. Note that due to Haskell's unspecified evaluation order, an expression may return one of several possible exceptions: consider the expression error "urk" + 1 `div` 0. Does tryAll return Just (ErrorCall "urk") or Just (ArithError DivideByZero)? The answer is "either": tryAll makes a non-deterministic choice about which exception to return. If you call it again, you might get a different exception back. This is ok, because tryAll is an IO computation.
tryAllIO is the same as tryAll except that the argument to evaluate is an IO computation. Don't try to use tryAll to catch exceptions in IO computations: in GHC an expression of type IO a is in fact a function, so evaluating it does nothing at all (and therefore raises no exceptions). Hence the need for tryAllIO, which runs IO computations properly.
The functions try and tryIO take an extra argument which is an exception predicate, a function which selects which type of exceptions we're interested in. The full set of exception predicates is given below:
justIoErrors :: Exception -> Maybe IOError justArithExceptions :: Exception -> Maybe ArithException justErrors :: Exception -> Maybe String justDynExceptions :: Exception -> Maybe Dynamic justAssertions :: Exception -> Maybe String justAsyncExceptions :: Exception -> Maybe AsyncException
For example, to catch just calls to 'error' we could use something like
result <- try justErrors thing_to_try
Any other exceptions which aren't matched by the predicate are re-raised, and may be caught by an enclosing try or catch.
The catch family is similar to the try family:
catchAll :: a -> (Exception -> IO a) -> IO a catchAllIO :: IO a -> (Exception -> IO a) -> IO a catch :: (Exception -> Maybe b) -> a -> (b -> IO a) -> IO a catchIO :: (Exception -> Maybe b) -> IO a -> (b -> IO a) -> IO a
The difference is that instead of returning an Either type as the result, the catch functions take a handler argument which is invoked in the case that an exception was raised while evaluating the first argument.
catch and catchIO take exception predicate arguments in the same way as try and tryIO.
Note that catchIO justIoErrors is identical to IO.catch. In fact, the implementation of IO errors in GHC uses exceptions "under the hood".
Also, don't forget to import Prelude hiding (catch) when using this library, to avoid the name clash between Exception.catch and IO.catch.
Because the Exception datatype isn't extensible, we added an interface for throwing and catching exceptions of type Dynamic (see Section 3.6), which allows exception values of any type in the Typeable class to be thrown and caught.
throwDyn :: Typeable exception => exception -> b catchDyn :: Typeable exception => IO a -> (exception -> IO a) -> IO a
The catchDyn function only catches exceptions of the required type; all other exceptions are re-thrown as with catchIO and friends above.
The bracket functions are useful for making sure that resources are released properly by code that may raise exceptions:
bracket :: IO a -> (a -> IO b) -> (a -> IO c) -> IO c bracket_ :: IO a -> IO b -> IO c -> IO c finally :: IO a -> IO b -> IO b
For example, to open a file, do some work on it and then close it again, we might use something like:
process_file = bracket (openFile "filename") closeFile (do ... )
bracket works as follows: it executes its first argument ("open"), then its third argument, followed finally by its second argument ("close"). If the third argument happened to raise an exception, then the close operation will still be performed, and the exception will be re-raised.
This means that in the example above the file will always be closed, even if an error occurs during processing.
The arguments to bracket are in this order so that we can partially apply it, like:
withFile name = bracket (openFile name) closeFile
The bracket_ function is a variant of bracket that throws away the result of the open, and finally is an even simpler version where we just want some closing code.