Update the Exception docs
authorIan Lynagh <igloo@earth.li>
Sat, 31 Jan 2009 20:48:45 +0000 (20:48 +0000)
committerIan Lynagh <igloo@earth.li>
Sat, 31 Jan 2009 20:48:45 +0000 (20:48 +0000)
Control/Exception.hs
Control/Exception/Base.hs
GHC/Conc.lhs
GHC/Exception.lhs
GHC/IOBase.lhs

index 1b29913..5ef9b42 100644 (file)
@@ -5,7 +5,7 @@
 -- 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)
@@ -25,6 +25,9 @@
 --  * /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 (
@@ -47,7 +50,7 @@ module Control.Exception (
         NestedAtomically(..),
 #endif
 #ifdef __NHC__
-        System.ExitCode(),     -- instance Exception
+        System.ExitCode(), -- instance Exception
 #endif
 
         BlockedOnDeadMVar(..),
@@ -61,11 +64,11 @@ module Control.Exception (
         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
@@ -75,24 +78,23 @@ module Control.Exception (
         -- '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
 
@@ -103,9 +105,9 @@ module Control.Exception (
         -- |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
 
@@ -117,15 +119,20 @@ module Control.Exception (
 
         -- * 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
@@ -142,8 +149,27 @@ import Prelude hiding (catch)
 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
 
@@ -230,3 +256,48 @@ until the point when the 'Control.Concurrent.MVar.takeMVar' succeeds.
 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.
+-}
+
index 468dd49..b803b5e 100644 (file)
@@ -332,31 +332,35 @@ blocked  = return False
 -- 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).
@@ -391,11 +395,14 @@ catch m h = Hugs.Exception.catchException m h'
 -- 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
@@ -410,7 +417,7 @@ catchJust p a handler = catch a handler'
 -- | 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
@@ -436,16 +443,14 @@ mapException f v = unsafePerformIO (catch (evaluate v)
 -- '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).
@@ -465,6 +470,8 @@ tryJust p a = do
                         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)
@@ -484,7 +491,7 @@ onException io what = io `catch` \e -> do what
 -- > 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.:
@@ -525,7 +532,7 @@ a `finally` sequel =
 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\")
@@ -547,6 +554,8 @@ assert False _ = throw (AssertionFailed "")
 -----
 
 #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")
 
@@ -564,6 +573,11 @@ instance Exception 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")
 
@@ -581,6 +595,9 @@ instance Exception 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")
 
@@ -598,6 +615,11 @@ instance Exception 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")
 
@@ -615,6 +637,9 @@ instance Exception 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")
 
@@ -632,6 +657,10 @@ instance Exception 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")
 
@@ -649,6 +678,8 @@ instance Exception 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")
 
index 78785ed..1d5cc9c 100644 (file)
@@ -306,7 +306,7 @@ Like any blocking operation, 'throwTo' is therefore interruptible (see Section 5
 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.
 
index ad10481..4dce281 100644 (file)
@@ -31,12 +31,105 @@ import GHC.Show
 %*********************************************************
 
 \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
@@ -63,6 +156,8 @@ throw e = raise# (toException 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
 
@@ -73,7 +168,7 @@ instance Show ErrorCall where
 
 -----
 
--- |The type of arithmetic exceptions
+-- |Arithmetic exceptions.
 data ArithException
   = Overflow
   | Underflow
index 48a0950..a952bd5 100644 (file)
@@ -634,6 +634,8 @@ showHandle file = showString "{handle: " . showString file . showString "}"
 -- ------------------------------------------------------------------------
 -- 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
 
@@ -647,6 +649,8 @@ blockedOnDeadMVar = toException BlockedOnDeadMVar
 
 -----
 
+-- |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
 
@@ -660,6 +664,8 @@ blockedIndefinitely = toException BlockedIndefinitely
 
 -----
 
+-- |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
 
@@ -670,6 +676,8 @@ instance Show Deadlock where
 
 -----
 
+-- |Exceptions generated by 'assert'. The @String@ gives information
+-- about the source location of the assertion.
 data AssertionFailed = AssertionFailed String
     deriving Typeable
 
@@ -680,7 +688,7 @@ instance Show AssertionFailed where
 
 -----
 
--- |Asynchronous exceptions
+-- |Asynchronous exceptions.
 data AsyncException
   = StackOverflow
         -- ^The current thread\'s stack exceeded its limit.
@@ -914,7 +922,7 @@ catchAny :: IO a -> (forall e . Exception e => e -> IO a) -> IO a
 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:
@@ -975,9 +983,10 @@ blocked = IO $ \s -> case asyncExceptionsBlocked# s of
 \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