-- Module : Control.Exception
-- Copyright : (c) The University of Glasgow 2001
-- License : BSD-style (see the file libraries/base/LICENSE)
---
+--
-- Maintainer : libraries@haskell.org
-- Stability : experimental
-- Portability : non-portable (extended exceptions)
-- * /Asynchronous exceptions in Haskell/, by Simon Marlow, Simon Peyton
-- Jones, Andy Moran and John Reppy, in /PLDI'01/.
--
+-- * /An Extensible Dynamically-Typed Hierarchy of Exceptions/,
+-- by Simon Marlow, in /Haskell '06/.
+--
-----------------------------------------------------------------------------
module Control.Exception (
NestedAtomically(..),
#endif
#ifdef __NHC__
- System.ExitCode(), -- instance Exception
+ System.ExitCode(), -- instance Exception
#endif
BlockedOnDeadMVar(..),
ErrorCall(..),
-- * Throwing exceptions
- throwIO, -- :: Exception -> IO a
- throw, -- :: Exception -> a
- ioError, -- :: IOError -> IO a
+ throw,
+ throwIO,
+ ioError,
#ifdef __GLASGOW_HASKELL__
- throwTo, -- :: ThreadId -> Exception -> a
+ throwTo,
#endif
-- * Catching Exceptions
-- 'IO' monad.
-- ** The @catch@ functions
- catch, -- :: IO a -> (Exception -> IO a) -> IO a
+ catch,
catches, Handler(..),
- catchJust, -- :: (Exception -> Maybe b) -> IO a -> (b -> IO a) -> IO a
+ catchJust,
-- ** The @handle@ functions
- handle, -- :: (Exception -> IO a) -> IO a -> IO a
- handleJust,-- :: (Exception -> Maybe b) -> (b -> IO a) -> IO a -> IO a
+ handle,
+ handleJust,
-- ** The @try@ functions
- try, -- :: IO a -> IO (Either Exception a)
- tryJust, -- :: (Exception -> Maybe b) -> a -> IO (Either b a)
- onException,
+ try,
+ tryJust,
-- ** The @evaluate@ function
- evaluate, -- :: a -> IO a
+ evaluate,
-- ** The @mapException@ function
- mapException, -- :: (Exception -> Exception) -> a -> a
+ mapException,
-- * Asynchronous Exceptions
-- |The following two functions allow a thread to control delivery of
-- asynchronous exceptions during a critical region.
- block, -- :: IO a -> IO a
- unblock, -- :: IO a -> IO a
- blocked, -- :: IO Bool
+ block,
+ unblock,
+ blocked,
-- *** Applying @block@ to an exception handler
-- * Assertions
- assert, -- :: Bool -> a -> a
+ assert,
-- * Utilities
- bracket, -- :: IO a -> (a -> IO b) -> (a -> IO c) -> IO ()
- bracket_, -- :: IO a -> IO b -> IO c -> IO ()
+ bracket,
+ bracket_,
bracketOnError,
- finally, -- :: IO a -> IO b -> IO a
+ finally,
+ onException,
+
+ -- * Catching all exceptions
+
+ -- $catchall
) where
import Control.Exception.Base
import System (ExitCode())
#endif
+-- | You need this when using 'catches'.
data Handler a = forall e . Exception e => Handler (e -> IO a)
+{- |
+Sometimes you want to catch two different sorts of exception. You could
+do something like
+
+> f = expr `catch` \ (ex :: ArithException) -> handleArith ex
+> `catch` \ (ex :: IOException) -> handleIO ex
+
+However, there are a couple of problems with this approach. The first is
+that having two exception handlers is inefficient. However, the more
+serious issue is that the second exception handler will catch exceptions
+in the first, e.g. in the example above, if @handleArith@ throws an
+@IOException@ then the second exception handler will catch it.
+
+Instead, we provide a function 'catches', which would be used thus:
+
+> f = expr `catches` [Handler (\ (ex :: ArithException) -> handleArith ex),
+> Handler (\ (ex :: IOException) -> handleIO ex)]
+-}
catches :: IO a -> [Handler a] -> IO a
catches io handlers = io `catch` catchesHandler handlers
Similar arguments apply for other interruptible operations like
'System.IO.openFile'.
-}
+
+{- $catchall
+
+It is possible to catch all exceptions, by using the type 'SomeException':
+
+> catch f (\e -> ... (e :: SomeException) ...)
+
+HOWEVER, this is normally not what you want to do!
+
+For example, suppose you want to read a file, but if it doesn't exist
+then continue as if it contained \"\". In the old exceptions library,
+the easy thing to do was just to catch all exceptions and return \"\" in
+the handler. However, this has all sorts of undesirable consequences.
+For example, if the user presses control-C at just the right moment then
+the 'UserInterrupt' exception will be caught, and the program will
+continue running under the belief that the file contains \"\".
+Similarly, if another thread tries to kill the thread reading the file
+then the 'ThreadKilled' exception will be ignored.
+
+Instead, you should only catch exactly the exceptions that you really
+want. In this case, this would likely be more specific than even
+\"any IO exception\"; a permissions error would likely also want to be
+handled differently. Instead, you would probably want something like:
+
+> catchJust (\e -> if isDoesNotExistErrorType (ioeGetErrorType e) then Just () else Nothing)
+> (readFile f)
+> (\_ -> return "")
+
+There are occassions when you really do need to catch any sort of
+exception. However, in most cases this is just so you can do some
+cleaning up; you aren't actually interested in the exception itself.
+For example, if you open a file then you want to close it again,
+whether processing the file executes normally or throws an exception.
+However, in these cases you can use functions like 'bracket', 'finally'
+and 'onException', which never actually pass you the exception, but
+just call the cleanup functions at the appropriate points.
+
+But sometimes you really do need to catch any exception, and actually
+see what the exception is. One example is at the very top-level of a
+program, you may wish to catch any exception, print it to a logfile or
+the screen, and then exit gracefully. For these cases, you can use
+'catch' (or one of the other exception-catching functions) with the
+'SomeException' type.
+-}
+
-- the \"handler\" is executed, with the value of the exception passed as an
-- argument. Otherwise, the result is returned as normal. For example:
--
--- > catch (openFile f ReadMode)
--- > (\e -> hPutStr stderr ("Couldn't open "++f++": " ++ show e))
+-- > catch (readFile f)
+-- > (\e -> do let err = show (e :: IOException)
+-- > hPutStr stderr ("Warning: Couldn't open " ++ f ++ ": " ++ err)
+-- > return "")
+--
+-- Note that we have to give a type signature to @e@, or the program
+-- will not typecheck as the type is ambiguous. While it is possible
+-- to catch exceptions of any type, see $catchall for an explanation
+-- of the problems with doing so.
--
-- For catching exceptions in pure (non-'IO') expressions, see the
-- function 'evaluate'.
--
-- 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
--- 'catch' execute the handler passing
--- @ErrorCall \"urk\"@, or @ArithError DivideByZero@?
---
--- The answer is \"either\": 'catch' makes a
--- non-deterministic choice about which exception to catch. If you
--- call it again, you might get a different exception back. This is
--- ok, because 'catch' is an 'IO' computation.
+-- expression may throw one of several possible exceptions: consider
+-- the expression @(error \"urk\") + (1 \`div\` 0)@. Does
+-- the expression throw
+-- @ErrorCall \"urk\"@, or @DivideByZero@?
--
--- Note that 'catch' catches all types of exceptions, and is generally
--- used for \"cleaning up\" before passing on the exception using
--- 'throwIO'. It is not good practice to discard the exception and
--- continue, without first checking the type of the exception (it
--- might be a 'ThreadKilled', for example). In this case it is usually better
--- to use 'catchJust' and select the kinds of exceptions to catch.
+-- The answer is \"it might throw either\"; the choice is
+-- non-deterministic. If you are catching any type of exception then you
+-- might catch either. If you are calling @catch@ with type
+-- @IO Int -> (ArithException -> IO Int) -> IO Int@ then the handler may
+-- get run with @DivideByZero@ as an argument, or an @ErrorCall \"urk\"@
+-- exception may be propogated further up. If you call it again, you
+-- might get a the opposite behaviour. This is ok, because 'catch' is an
+-- 'IO' computation.
--
--- Also note that the "Prelude" also exports a function called
+-- Note that the "Prelude" also exports a function called
-- 'Prelude.catch' with a similar type to 'Control.Exception.catch',
-- except that the "Prelude" version only catches the IO and user
-- families of exceptions (as required by Haskell 98).
-- argument which is an /exception predicate/, a function which
-- selects which type of exceptions we\'re interested in.
--
--- > result <- catchJust errorCalls thing_to_try handler
+-- > catchJust (\e -> if isDoesNotExistErrorType (ioeGetErrorType e) then Just () else Nothing)
+-- > (readFile f)
+-- > (\_ -> do hPutStrLn stderr ("No such file: " ++ show f)
+-- > return "")
--
-- Any other exceptions which are not matched by the predicate
-- are re-raised, and may be caught by an enclosing
--- 'catch' or 'catchJust'.
+-- 'catch', 'catchJust', etc.
catchJust
:: Exception e
=> (e -> Maybe b) -- ^ Predicate to select exceptions
-- | A version of 'catch' with the arguments swapped around; useful in
-- situations where the code for the handler is shorter. For example:
--
--- > do handle (\e -> exitWith (ExitFailure 1)) $
+-- > do handle (\NonTermination -> exitWith (ExitFailure 1)) $
-- > ...
handle :: Exception e => (e -> IO a) -> IO a -> IO a
handle = flip catch
-- 'try' and variations.
-- | Similar to 'catch', but returns an 'Either' result which is
--- @('Right' a)@ if no exception was raised, or @('Left' e)@ if an
--- exception was raised and its value is @e@.
+-- @('Right' a)@ if no exception of type @e@ was raised, or @('Left' ex)@
+-- if an exception of type @e@ was raised and its value is @ex@.
+-- If any other type of exception is raised than it will be propogated
+-- up to the next enclosing exception handler.
--
-- > try a = catch (Right `liftM` a) (return . Left)
--
--- Note: as with 'catch', it is only polite to use this variant if you intend
--- to re-throw the exception after performing whatever cleanup is needed.
--- Otherwise, 'tryJust' is generally considered to be better.
---
--- Also note that "System.IO.Error" also exports a function called
+-- Note that "System.IO.Error" also exports a function called
-- 'System.IO.Error.try' with a similar type to 'Control.Exception.try',
-- except that it catches only the IO and user families of exceptions
-- (as required by the Haskell 98 @IO@ module).
Nothing -> throw e
Just b -> return (Left b)
+-- | Like 'finally', but only performs the final action if there was an
+-- exception raised by the computation.
onException :: IO a -> IO b -> IO a
onException io what = io `catch` \e -> do what
throw (e :: SomeException)
-- > bracket
-- > (openFile "filename" ReadMode)
-- > (hClose)
--- > (\handle -> do { ... })
+-- > (\fileHandle -> do { ... })
--
-- The arguments to 'bracket' are in this order so that we can partially apply
-- it, e.g.:
bracket_ :: IO a -> IO b -> IO c -> IO c
bracket_ before after thing = bracket before (const after) (const thing)
--- | Like bracket, but only performs the final action if there was an
+-- | Like 'bracket', but only performs the final action if there was an
-- exception raised by the in-between computation.
bracketOnError
:: IO a -- ^ computation to run first (\"acquire resource\")
-----
#if __GLASGOW_HASKELL__ || __HUGS__
+-- |A pattern match failed. The @String@ gives information about the
+-- source location of the pattern.
data PatternMatchFail = PatternMatchFail String
INSTANCE_TYPEABLE0(PatternMatchFail,patternMatchFailTc,"PatternMatchFail")
-----
+-- |A record selector was applied to a constructor without the
+-- appropriate field. This can only happen with a datatype with
+-- multiple constructors, where some fields are in one constructor
+-- but not another. The @String@ gives information about the source
+-- location of the record selector.
data RecSelError = RecSelError String
INSTANCE_TYPEABLE0(RecSelError,recSelErrorTc,"RecSelError")
-----
+-- |An uninitialised record field was used. The @String@ gives
+-- information about the source location where the record was
+-- constructed.
data RecConError = RecConError String
INSTANCE_TYPEABLE0(RecConError,recConErrorTc,"RecConError")
-----
+-- |A record update was performed on a constructor without the
+-- appropriate field. This can only happen with a datatype with
+-- multiple constructors, where some fields are in one constructor
+-- but not another. The @String@ gives information about the source
+-- location of the record update.
data RecUpdError = RecUpdError String
INSTANCE_TYPEABLE0(RecUpdError,recUpdErrorTc,"RecUpdError")
-----
+-- |A class method without a definition (neither a default definition,
+-- nor a definition in the appropriate instance) was called. The
+-- @String@ gives information about which method it was.
data NoMethodError = NoMethodError String
INSTANCE_TYPEABLE0(NoMethodError,noMethodErrorTc,"NoMethodError")
-----
+-- |Thrown when the runtime system detects that the computation is
+-- guaranteed not to terminate. Note that there is no guarantee that
+-- the runtime system will notice whether any given computation is
+-- guaranteed to terminate or not.
data NonTermination = NonTermination
INSTANCE_TYPEABLE0(NonTermination,nonTerminationTc,"NonTermination")
-----
+-- |Thrown when the program attempts to call @atomically@, from the @stm@
+-- package, inside another call to @atomically@.
data NestedAtomically = NestedAtomically
INSTANCE_TYPEABLE0(NestedAtomically,nestedAtomicallyTc,"NestedAtomically")
the paper).
There is currently no guarantee that the exception delivered by 'throwTo' will be
-delivered at the first possible opportunity. In particular, if a thread may
+delivered at the first possible opportunity. In particular, a thread may
unblock and then re-block exceptions (using 'unblock' and 'block') without receiving
a pending 'throwTo'. This is arguably undesirable behaviour.
%*********************************************************
\begin{code}
+{- |
+The @SomeException@ type is the root of the exception type hierarchy.
+When an exception of type @e@ is thrown, behind the scenes it is
+encapsulated in a @SomeException@.
+-}
data SomeException = forall e . Exception e => SomeException e
deriving Typeable
instance Show SomeException where
showsPrec p (SomeException e) = showsPrec p e
+{- |
+Any type that you wish to throw or catch as an exception must be an
+instance of the @Exception@ class. The simplest case is a new exception
+type directly below the root:
+
+> data MyException = ThisException | ThatException
+> deriving (Show, Typeable)
+>
+> instance Exception MyException
+
+The default method definitions in the @Exception@ class do what we need
+in this case. You can now throw and catch @ThisException@ and
+@ThatException@ as exceptions:
+
+@
+*Main> throw ThisException `catch` \e -> putStrLn (\"Caught \" ++ show (e :: MyException))
+Caught ThisException
+@
+
+In more complicated examples, you may wish to define a whole hierarchy
+of exceptions:
+
+> ---------------------------------------------------------------------
+> -- Make the root exception type for all the exceptions in a compiler
+>
+> data SomeCompilerException = forall e . Exception e => SomeCompilerException e
+> deriving Typeable
+>
+> instance Show SomeCompilerException where
+> show (SomeCompilerException e) = show e
+>
+> instance Exception SomeCompilerException
+>
+> compilerExceptionToException :: Exception e => e -> SomeException
+> compilerExceptionToException = toException . SomeCompilerException
+>
+> compilerExceptionFromException :: Exception e => SomeException -> Maybe e
+> compilerExceptionFromException x = do
+> SomeCompilerException a <- fromException x
+> cast a
+>
+> ---------------------------------------------------------------------
+> -- Make a subhierarchy for exceptions in the frontend of the compiler
+>
+> data SomeFrontendException = forall e . Exception e => SomeFrontendException e
+> deriving Typeable
+>
+> instance Show SomeFrontendException where
+> show (SomeFrontendException e) = show e
+>
+> instance Exception SomeFrontendException where
+> toException = compilerExceptionToException
+> fromException = compilerExceptionFromException
+>
+> frontendExceptionToException :: Exception e => e -> SomeException
+> frontendExceptionToException = toException . SomeFrontendException
+>
+> frontendExceptionFromException :: Exception e => SomeException -> Maybe e
+> frontendExceptionFromException x = do
+> SomeFrontendException a <- fromException x
+> cast a
+>
+> ---------------------------------------------------------------------
+> -- Make an exception type for a particular frontend compiler exception
+>
+> data MismatchedParentheses = MismatchedParentheses
+> deriving (Typeable, Show)
+>
+> instance Exception MismatchedParentheses where
+> toException = frontendExceptionToException
+> fromException = frontendExceptionFromException
+
+We can now catch a @MismatchedParentheses@ exception as
+@MismatchedParentheses@, @SomeFrontendException@ or
+@SomeCompilerException@, but not other types, e.g. @IOException@:
+
+@
+*Main> throw MismatchedParentheses `catch` \e -> putStrLn (\"Caught \" ++ show (e :: MismatchedParentheses))
+Caught MismatchedParentheses
+*Main> throw MismatchedParentheses `catch` \e -> putStrLn (\"Caught \" ++ show (e :: SomeFrontendException))
+Caught MismatchedParentheses
+*Main> throw MismatchedParentheses `catch` \e -> putStrLn (\"Caught \" ++ show (e :: SomeCompilerException))
+Caught MismatchedParentheses
+*Main> throw MismatchedParentheses `catch` \e -> putStrLn (\"Caught \" ++ show (e :: IOException))
+*** Exception: MismatchedParentheses
+@
+
+-}
class (Typeable e, Show e) => Exception e where
toException :: e -> SomeException
fromException :: SomeException -> Maybe e
\end{code}
\begin{code}
+-- |This is thrown when the user calls 'error'. The @String@ is the
+-- argument given to 'error'.
data ErrorCall = ErrorCall String
deriving Typeable
-----
--- |The type of arithmetic exceptions
+-- |Arithmetic exceptions.
data ArithException
= Overflow
| Underflow
-- ------------------------------------------------------------------------
-- Exception datatypes and operations
+-- |The thread is blocked on an @MVar@, but there are no other references
+-- to the @MVar@ so it can't ever continue.
data BlockedOnDeadMVar = BlockedOnDeadMVar
deriving Typeable
-----
+-- |The thread is awiting to retry an STM transaction, but there are no
+-- other references to any @TVar@s involved, so it can't ever continue.
data BlockedIndefinitely = BlockedIndefinitely
deriving Typeable
-----
+-- |There are no runnable threads, so the program is deadlocked.
+-- The @Deadlock@ exception is raised in the main thread only.
data Deadlock = Deadlock
deriving Typeable
-----
+-- |Exceptions generated by 'assert'. The @String@ gives information
+-- about the source location of the assertion.
data AssertionFailed = AssertionFailed String
deriving Typeable
-----
--- |Asynchronous exceptions
+-- |Asynchronous exceptions.
data AsyncException
= StackOverflow
-- ^The current thread\'s stack exceeded its limit.
catchAny (IO io) handler = IO $ catch# io handler'
where handler' (SomeException e) = unIO (handler e)
--- | A variant of 'throw' that can be used within the 'IO' monad.
+-- | A variant of 'throw' that can only be used within the 'IO' monad.
--
-- Although 'throwIO' has a type that is an instance of the type of 'throw', the
-- two functions are subtly different:
\end{code}
\begin{code}
--- | Forces its argument to be evaluated when the resultant 'IO' action
--- is executed. It can be used to order evaluation with respect to
--- other 'IO' operations; its semantics are given by
+-- | Forces its argument to be evaluated to weak head normal form when
+-- the resultant 'IO' action is executed. It can be used to order
+-- evaluation with respect to other 'IO' operations; its semantics are
+-- given by
--
-- > evaluate x `seq` y ==> y
-- > evaluate x `catch` f ==> (return $! x) `catch` f