The basic example opens a file for reading (this may fail because the file does not exist, is not readable, etc.), reads two characters (this may fail because EOF, etc.), closes the file, and prints the two characters.
If any of the mentioned failure happens, we just print the exception message and give up.
(The printing part may also fail, but let's not worry about that.)
import Control.Exception ( IOException, try ) import System.IO ( IOMode(ReadMode), withFile, hGetChar ) main = do e <- try (withFile "file.txt" ReadMode (\handle -> do char1 <- hGetChar handle char2 <- hGetChar handle return (char1, char2) )) case e of Left exc -> print (exc :: IOException) -- the need for this type annotation will be explained soon Right (char1, char2) -> putStrLn [char1, char2]
withFile
: Opens file at the beginning, closes file at the end.
The latter is in fact a “finally close file”, it closes file when an exception
happens, too. Please prefer withFile
to openFile
and
hClose
.
try
:
Left
.
IOException
by my type annotation.
Right
.There are many different types of exceptions; every instance of the type
class Exception
is one. Each type furthermore has different values
to stand for different variants. To be concrete, two standard examples:
Type IOException
includes EOF, file already exists (if you
try to create), file not found (if you try to open), file permission errors,
etc. Predicates for distinguishing them are in System.IO.Error.
Type AsyncException
has 4 values standing for stack overflow,
heap overflow, thread killed, and user interrupt (Control-C), respectively.
try
is polymorphic in the type of exceptions:
try :: Exception e => IO a -> IO (Either e a)
This polymorphism does not mean that it intercepts all exceptions. It means that it intercepts one exception type you choose, and propagates (re-throws) all other exception types. You have to choose one.
You have to somehow help the computer figure out which type for
e
you choose. Sometimes, your code contains enough context for this
to be inferred; other times, you have to add that context yourself by
handwritten types. My example shows one way: type annotation in an
expression:
print (exc :: IOException)
Another way: type annotation in a pattern (requires a language extension):
{-# LANGUAGE ScopedTypeVariables #-} import Control.Exception ( IOException, try ) import System.IO ( IOMode(ReadMode), withFile, hGetChar ) main = do e <- try (withFile "file.txt" ReadMode (\handle -> do char1 <- hGetChar handle char2 <- hGetChar handle return (char1, char2) )) case e of Left (exc :: IOException) -> print exc Right (char1, char2) -> putStrLn [char1, char2]
Third way: create and use a helper with a specific type signature:
import Control.Exception ( IOException, try ) import System.IO ( IOMode(ReadMode), withFile, hGetChar ) tryIOE :: IO a -> IO (Either IOException a) tryIOE = try main = do e <- tryIOE (withFile "file.txt" ReadMode (\handle -> do char1 <- hGetChar handle char2 <- hGetChar handle return (char1, char2) )) case e of Left exc -> print exc Right (char1, char2) -> putStrLn [char1, char2]
Advice: Most likely you handle only one exception type at a time (even only a subset of the variants of that type). Most unlikely you handle “all possible exceptions whatsoever” the same way. One single type at a time is exactly right. I am talking about handling (fall-back plans), not clean-up (tear-down code); clean-up is a separate story.
It is true that your clean-up code must be run for all exceptions; it is not true that you have to catch all exceptions, in your very own person, for this purpose. The standard library already provides enough clean-up tools. Do not re-invent the wheel.
(It is true that the library tools accomplish this by catching all exceptions. See below supertyping.)
Prefer things like withFile
(System.IO) and
withArray
(Foreign.Marshal.Array), which clean up after themselves,
exceptions or no exceptions. You need write no set-up code or tear-down code
yourself.
If you need to use your own set-up and tear-down code, use them with
bracket
. For example, withFile
is literally:
withFile path mode use = bracket (openFile path mode) hClose use
The set-up code is openFile path mode
, the tear-down code is
hClose
.
If you don't have set-up code, just tear-down code, use finally
.
The poster-boy example creates a thread and waits for it to die. The plot:
parent thread creates an MVar, child thread does a putMVar at the end, parent
does a takeMVar to wait for it. So child thread does not have set-up code, only
tear-down code for the putMVar.
import Control.Exception ( finally ) import Control.Concurrent ( takeMVar, putMVar, newEmptyMVar, forkIO ) main = do fin <- newEmptyMVar forkIO (child_code `finally` putMVar fin ()) -- possibly do some other things now, before... takeMVar fin child_code = ...
You probably wonder why I use try
but not catch
:
import Control.Exception ( IOException, catch ) import System.IO ( IOMode(ReadMode), withFile, hGetChar ) main = do catch (withFile "file.txt" ReadMode (\handle -> do char1 <- hGetChar handle char2 <- hGetChar handle putStrLn [char1, char2] )) (\exc -> print (exc :: IOException))
A minor issue is that the printing is now also inside the catch-block, which is not what I intend. But I'm sure it does not matter in this example, and you can figure out workarounds in real cases where it matters. It's a minor issue.
The major issue is with how the handler is run. The handler is run with
asynchronous exceptions masked. (Recall that asychronous exceptions are
represented by AsyncException
and has 4 cases, among them you most
likely care about thread killed and user interrupt.)
This masking is a good idea if you want the handler to clean up. But you
already have bracket
and finally
(they use
catch
internally of course). You need not use catch
in
your very own person for this.
This masking is a bad idea if your handler takes a long time. Your program becomes unresponsive to important events. Recall that, for example, if thread 1 kills thread 0, but thread 0 is masking it, then thread 1 cannot move on either. Unresponsiveness is a domino effect, one thread can hang them all.
This masking looks harmless otherwise, but is an error-prone habit. One
day, you may have to write code for: attempt an I/O action, if exception, loop
back and attempt again. If you habitually use catch
, you will write
like this:
loop = catch askUser (\exc -> loop)
This uses Θ(n) space if it runs n iterations: Each time you enter a masking context you occupy Θ(1) space, not freed until you leave it. Worse, the last n-1 of the iterations are run masked, which is very likely unintended.
In contrast, it is easy to use try
, spend O(1) space,
and not erroneously mask:
loop = try askUser >>= either (\exc -> loop) return
It is better to have the habit of preferring try
and be
very consciously judicious when you use catch
.
I have said: Most likely you handle only one exception type at a time, even
only a subset of the variants of that type. The operations tryJust
and catchJust
help you handle only a subset.
The type of tryJust
:
tryJust :: Exception e => (e -> Maybe b) -> IO a -> IO (Either b a)
You provide a function of type e -> Maybe b
to say which subset
you want to intercept. (Anything else is propagated or re-thrown.) So you write
this function to examine the exception, then give Nothing
to mean
you don't want to intercept, Just v
to mean you want to
intercept, and in this case what value v
you want to appear in the
Left v
returned by tryJust
.
The following example shows how to intercept EOF only.
import Control.Exception ( IOException, tryJust ) import System.IO ( IOMode(ReadMode), withFile, hGetChar ) import System.IO.Error ( isEOFError ) main = do r <- tryJust predicate (withFile "file.txt" ReadMode hGetChar) case r of Left () -> putStrLn "ended" Right c -> print c predicate :: IOException -> Maybe () predicate e | isEOFError e = Just () | otherwise = Nothing
catchJust
is similar.
I have hesitated to tell you how to intercept all exceptions whatsoever. But
eventually, you should still know, for curiosity (e.g., how bracket
does it) or for rare applications.
When you choose which exception type to intercept, if you choose
SomeException
, then you will intercept all exceptions
whatsoever.
If you want to argue over “SomeException” vs “AllException”, your time is better spent on reading my Any, For All, Exists.
SomeException
is an existential type wrapping around actual
exceptions. This is how all different exception types can be unified under
one type. To help differentiate them when needed, exception types are
instances of Typeable
(in module Data.Typeable),
a kind of runtime type recovery.
I will illustrate with a rare but useful application. Sometimes you know that
an IO action will throw an exception, but you don't know which type yet, but you
need to know because in production code you will want to intercept precisely
that type. Here is one way to obtain a clue. Write experimental code to run that
IO action and intercept SomeException
, unwrap it (pattern matching
will do), and use a Typeable
operation to print its type
name. That gives you something to explore or search.
import Control.Exception ( SomeException(SomeException), try ) import Data.Typeable ( typeOf ) main = do r <- try getChar case r of Right _ -> return () Left (SomeException e) -> print (typeOf e)
Another rare application is when your tear-down code is dependent on what
exception type you get (or lack thereof). So bracket
and
finally
won't cut it because they won't tell you. And you will want
to work with SomeException so you cover all cases, and then query what actual
type it wraps for type-dependent tear down. Now, this is so rare in sequential
programs that the library doesn't have a ready-made tool for you. But it is
often enough in multi-threaded programs that Control.Concurrent has one such
thing:
forkFinally :: IO a -> (Either SomeException a -> IO ()) -> IO ThreadId
The meaning is this: The first parameter is the main job to run in the new
thread. The second parameter is the tear-down job (to run in the new thread too,
just before it dies). The tear-down job is typically sending out a notification,
either “done, the answer is…” or “aborted, here is why…”, so it really needs
to be Either SomeException a -> IO ()
to receive full information
on how and why the thread quits. Here is how to use this information:
{-# LANGUAGE ScopedTypeVariables #-} import Control.Exception ( Exception(fromException), AsyncException, IOException ) import Control.Concurrent ( forkFinally, newEmptyMVar, putMVar, takeMVar ) data Msg a = Good a | Interrupted | BadIO IOException | BadOther main = do mbox <- newEmptyMVar tID <- forkFinally job (bye mbox) -- Perhaps some time later m <- takeMVar mbox case m of Good a -> ... Interrupted -> ... BadIO e -> ... BadOther -> ... job = ... bye mbox (Right a) = putMVar mbox (Good a) bye mbox (Left someexc) = case fromException someexc of Just (e :: AsyncException) -> putMVar mbox Interrupted Nothing -> case fromException someexc of Just (e :: IOException) -> putMVar mbox (BadIO e) Nothing -> putMVar mbox BadOther
fromException
is a method of class Exception
. It has type
fromException :: Exception e => SomeException -> Maybe e
and is useful for guessing and checking the wrapped type—you get a Just
for guessing right, Nothing
for guessing wrong. I use type annotation
in the Just
patterns to state what I guess.
The default implementation of fromException
is something you
could write yourself:
fromException (SomeException e) = cast e
So it just uses pattern matching to unwrap, then uses cast
from
Data.Typeable to guess and check. It is OK if you want to do that yourself instead of
calling fromException
.
You are now oriented and ready to read the docs in detail: Control.Exception documentation.
I have more Haskell Notes and Examples