Fix Alternative instance for IO
The Alternative instance for IO is currently defined in GHC.Base, using
(<|>) = mplusIO
Hunting further, mplusIO is defined in GHC.IO as
mplusIO :: IO a -> IO a -> IO a
mplusIO m n = m `catchException` \ (_ :: IOError) -> n
Why this is (possibly) wrong
Quoting documentation in Control.Exception
Applying mask to an exception handler
There's an implied mask around every exception handler in a call to one of the catch family of functions. This is because that is what you want most of the time - it eliminates a common race condition in starting an exception handler, because there may be no exception handler on the stack to handle another exception if one arrives immediately. If asynchronous exceptions are masked on entering the handler, though, we have time to install a new exception handler before being interrupted. If this weren't the default, one would have to write something like
mask $ \restore -> catch (restore (...)) (\e -> handler)
If you need to unmask asynchronous exceptions again in the exception handler, restore can be used there too.
Note that try and friends do not have a similar default, because there is no exception handler in this case. Don't use try for recovering from an asynchronous exception.
Being that mplusIO
only catches exceptions of type IOError
, it is not suited for cleanup work. It is more likely useful for running a fallback action should the first one fail. In this case, one would not want to prevent asynchronous exceptions from being able to interrupt the application in the alternative branch.
So I would suggest a better implementation for mplusIO
should be based on try
rather than catch
Something like:
mplusIO :: IO a -> IO a -> IO a
mplusIO m n = do
res <- catchException (m >>= \ v -> return (Right v)) (\e -> return (Left e))
case res of
Right a -> return a
Left (e :: IOError) -> n