From c856e1e71c608e8c291218f66645eb748270b6d2 Mon Sep 17 00:00:00 2001 From: Ian Lynagh Date: Sat, 31 Jan 2009 20:48:45 +0000 Subject: [PATCH] Update the Exception docs --- Control/Exception.hs | 115 ++++++++++++++++++++++++++++++++++++--------- Control/Exception/Base.hs | 91 +++++++++++++++++++++++------------ GHC/Conc.lhs | 2 +- GHC/Exception.lhs | 97 +++++++++++++++++++++++++++++++++++++- GHC/IOBase.lhs | 19 ++++++-- 5 files changed, 265 insertions(+), 59 deletions(-) diff --git a/Control/Exception.hs b/Control/Exception.hs index 1b29913..5ef9b42 100644 --- a/Control/Exception.hs +++ b/Control/Exception.hs @@ -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. +-} + diff --git a/Control/Exception/Base.hs b/Control/Exception/Base.hs index 468dd49..b803b5e 100644 --- a/Control/Exception/Base.hs +++ b/Control/Exception/Base.hs @@ -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") diff --git a/GHC/Conc.lhs b/GHC/Conc.lhs index 78785ed..1d5cc9c 100644 --- a/GHC/Conc.lhs +++ b/GHC/Conc.lhs @@ -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. diff --git a/GHC/Exception.lhs b/GHC/Exception.lhs index ad10481..4dce281 100644 --- a/GHC/Exception.lhs +++ b/GHC/Exception.lhs @@ -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 diff --git a/GHC/IOBase.lhs b/GHC/IOBase.lhs index 48a0950..a952bd5 100644 --- a/GHC/IOBase.lhs +++ b/GHC/IOBase.lhs @@ -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 -- 1.7.10.4