All code that runs in IO can experience exceptions of any type.
Remember you can only catch an exception in IO. But it is possible to throw exceptions from pure code. An example of this is
Throw doesn't throw until forced
Due to laziness the order of evaluation of expressions can be modified by the compiler. This means that throw can return an
exception which is inside a thunk that is evaluated at the wrong time and thus is evaluated outside of your
Prefer throwIO to throw for throwing an exception as throwIO piggybacks the strong ordering guarantees that the RTS provides for executing IO actions. Otherwise you are likely to run in to issues where the exception is evaluated at the wrong time due to laziness.
Prefer throwIO to throw
Since exceptions are only thrown when they are evaluated to the level of the exception we prefer the use of throwIO to throw. throwIO ties the raising of the exception to execution. Evaluating the IO action won't raise now. Only executing the IO action will raise the exception. The benefit of this over just throw is that the runtime system gives special ordering guarantees for IO actions.
This means that our exception will always be raised at the correct time and cannot hide inside unevaluated expressions since the raising of the exception isn'rt tied to evaluation anymore. We know the exception raising is now independent of evaluation with throwIO as even if you evaluate the IO value fully the exception will not be raised.
Think about the IO action 'readFile "non_existent.txt"'. Evaluating it cannot raise an exception. It only throws when it actually the IO action is executed.
If you use "throwIO :: Exception e => e -> IO a" it guarantees that the exception is raised before the "IO a" finishes. This guarantees it can't escape from the "IO a" part of catch (because catch only terminates after the first argument finishes and throwIO guarantees raising the exception before then)
evaluate . force can be used to flush out exceptions from a value by fully evaluating the expreession to normal form. This is
wasteful as the full evaluation will take place even if there is no exception present and thus throwIO should be preferred.
the exception at a point in time where we know it will be catched.
So far we have only takes about synchronous expressions.
Async functions are raised as a result of an external event to a given thread. They are thrown from one thread to another
and therefore can happen at any time.
To identify an async exception look at the way it is thrown. Async exceptions use
throwTo whereas sync exceptions use
Why catch exceptions
There are two reasons to catch an exception
- Cleanup - you catch, perform the cleanup action and rethrow. This is ok for an async exception.
- Recover - Catch the exceptions and continue something else without rethrowing. This is NOT OK for async exceptions.
You cannot recover from an asynchronous exception. You shouldn't try. You catch async exceptions, clean up and then rethrow the exception.
Unlike sync exceptions, the async bretherin are rethrown by exception handling mechanisms
Exception handlers work differently for async functions in that they will rethrow them at some point since you can't recover from them.
The functions ,
handle immediately rethrow async exceptions and not allow you to recover.
finally will allow you to clean up from the exception before they rethrow the async exception.
These functions don't actually give you access to the exception itself, they just call your clean up function.
Avoid low level masking whenever possible
Masking is complicated so try and use higher level functions. You can use helper functions 99% of the time.
The catch'em all anti-pattern
When using catch be granular about the exact exception you are catching.
There are loads of possible exceptions which can occur. For example you might get a
UserInterrupt exception a result
of the user hitting ^c in an attempt to kill the running program. Do you really want to handle this case? Probably only if you are cleaning up
Most of the time catching all exceptions is the right thing to do if you are performing clean up. A case where this also
makes sense is at the top level of your program if you want to print the exception and then exit.
To catch all exceptions you would use the
SomeExceptionType which covers all possible exceptions.
catch f (\e -> ... (e :: SomeException) ...)
Handling different exceptions in the same catch clause
f = expr `catches` [Handler (\ (ex :: ArithException) -> handleArith ex), Handler (\ (ex :: IOException) -> handleIO ex)]```