{-# LANGUAGE CPP #-}
#ifdef __GLASGOW_HASKELL__
{-# LANGUAGE DeriveDataTypeable #-}
#endif
module UnexceptionalIO (
UIO,
Unexceptional(..),
fromIO,
#ifdef __GLASGOW_HASKELL__
fromIO',
#endif
run,
runEitherIO,
unsafeFromIO,
SomeNonPseudoException,
#ifdef __GLASGOW_HASKELL__
PseudoException(..),
ProgrammerError(..),
ExternalError(..),
bracket,
#if MIN_VERSION_base(4,7,0)
forkFinally,
fork,
ChildThreadError(..)
#endif
#endif
) where
import Data.Maybe (fromMaybe)
import Control.Applicative (Applicative(..), (<|>), (<$>))
import Control.Monad (liftM, ap, (<=<))
import Control.Monad.Fix (MonadFix(..))
#ifdef __GLASGOW_HASKELL__
import System.Exit (ExitCode)
import Control.Exception (try)
import Data.Typeable (Typeable)
import qualified Control.Exception as Ex
import qualified Control.Concurrent as Concurrent
#if MIN_VERSION_base(4,11,0)
import qualified Control.Exception.Base as Ex
#endif
data PseudoException =
ProgrammerError ProgrammerError |
ExternalError ExternalError |
Exit ExitCode
deriving (Show, Typeable)
instance Ex.Exception PseudoException where
toException (ProgrammerError e) = Ex.toException e
toException (ExternalError e) = Ex.toException e
toException (Exit e) = Ex.toException e
fromException e =
ProgrammerError <$> Ex.fromException e <|>
ExternalError <$> Ex.fromException e <|>
Exit <$> Ex.fromException e
data ProgrammerError =
#if MIN_VERSION_base(4,9,0)
TypeError Ex.TypeError |
#endif
ArithException Ex.ArithException |
ArrayException Ex.ArrayException |
AssertionFailed Ex.AssertionFailed |
ErrorCall Ex.ErrorCall |
NestedAtomically Ex.NestedAtomically |
NoMethodError Ex.NoMethodError |
PatternMatchFail Ex.PatternMatchFail |
RecConError Ex.RecConError |
RecSelError Ex.RecSelError |
RecUpdError Ex.RecSelError
deriving (Show, Typeable)
instance Ex.Exception ProgrammerError where
#if MIN_VERSION_base(4,9,0)
toException (TypeError e) = Ex.toException e
#endif
toException (ArithException e) = Ex.toException e
toException (ArrayException e) = Ex.toException e
toException (AssertionFailed e) = Ex.toException e
toException (ErrorCall e) = Ex.toException e
toException (NestedAtomically e) = Ex.toException e
toException (NoMethodError e) = Ex.toException e
toException (PatternMatchFail e) = Ex.toException e
toException (RecConError e) = Ex.toException e
toException (RecSelError e) = Ex.toException e
toException (RecUpdError e) = Ex.toException e
fromException e =
#if MIN_VERSION_base(4,9,0)
TypeError <$> Ex.fromException e <|>
#endif
ArithException <$> Ex.fromException e <|>
ArrayException <$> Ex.fromException e <|>
AssertionFailed <$> Ex.fromException e <|>
ErrorCall <$> Ex.fromException e <|>
NestedAtomically <$> Ex.fromException e <|>
NoMethodError <$> Ex.fromException e <|>
PatternMatchFail <$> Ex.fromException e <|>
RecConError <$> Ex.fromException e <|>
RecSelError <$> Ex.fromException e <|>
RecUpdError <$> Ex.fromException e
data ExternalError =
#if MIN_VERSION_base(4,10,0)
CompactionFailed Ex.CompactionFailed |
#endif
#if MIN_VERSION_base(4,11,0)
FixIOException Ex.FixIOException |
#endif
#if MIN_VERSION_base(4,7,0)
AsyncException Ex.SomeAsyncException |
#else
AsyncException Ex.AsyncException |
#endif
BlockedIndefinitelyOnSTM Ex.BlockedIndefinitelyOnSTM |
BlockedIndefinitelyOnMVar Ex.BlockedIndefinitelyOnMVar |
Deadlock Ex.Deadlock |
NonTermination Ex.NonTermination
deriving (Show, Typeable)
instance Ex.Exception ExternalError where
#if MIN_VERSION_base(4,10,0)
toException (CompactionFailed e) = Ex.toException e
#endif
#if MIN_VERSION_base(4,11,0)
toException (FixIOException e) = Ex.toException e
#endif
toException (AsyncException e) = Ex.toException e
toException (BlockedIndefinitelyOnMVar e) = Ex.toException e
toException (BlockedIndefinitelyOnSTM e) = Ex.toException e
toException (Deadlock e) = Ex.toException e
toException (NonTermination e) = Ex.toException e
fromException e =
#if MIN_VERSION_base(4,10,0)
CompactionFailed <$> Ex.fromException e <|>
#endif
#if MIN_VERSION_base(4,11,0)
FixIOException <$> Ex.fromException e <|>
#endif
AsyncException <$> Ex.fromException e <|>
BlockedIndefinitelyOnSTM <$> Ex.fromException e <|>
BlockedIndefinitelyOnMVar <$> Ex.fromException e <|>
Deadlock <$> Ex.fromException e <|>
NonTermination <$> Ex.fromException e
newtype SomeNonPseudoException = SomeNonPseudoException Ex.SomeException deriving (Show, Typeable)
instance Ex.Exception SomeNonPseudoException where
toException (SomeNonPseudoException e) = e
fromException e = case Ex.fromException e of
Just pseudo -> const Nothing (pseudo :: PseudoException)
Nothing -> Just (SomeNonPseudoException e)
throwIO :: (Ex.Exception e) => e -> IO a
throwIO = Ex.throwIO
#else
import System.IO.Error (IOError, ioError, try)
type SomeNonPseudoException = IOError
throwIO :: SomeNonPseudoException -> IO a
throwIO = ioError
#endif
newtype UIO a = UIO (IO a)
instance Functor UIO where
fmap = liftM
instance Applicative UIO where
pure = return
(<*>) = ap
instance Monad UIO where
return = UIO . return
(UIO x) >>= f = UIO (x >>= run . f)
#if !MIN_VERSION_base(4,13,0)
fail s = error $ "UnexceptionalIO cannot fail (" ++ s ++ ")"
#endif
instance MonadFix UIO where
mfix f = UIO (mfix $ run . f)
class (Monad m) => Unexceptional m where
lift :: UIO a -> m a
instance Unexceptional UIO where
lift = id
instance Unexceptional IO where
lift = run
fromIO :: (Unexceptional m) => IO a -> m (Either SomeNonPseudoException a)
fromIO = unsafeFromIO . try
#ifdef __GLASGOW_HASKELL__
fromIO' :: (Ex.Exception e, Unexceptional m) =>
(SomeNonPseudoException -> e)
-> IO a
-> m (Either e a)
fromIO' f = liftM (either (Left . f) id) . fromIO . try
#endif
run :: UIO a -> IO a
run (UIO io) = io
#ifdef __GLASGOW_HASKELL__
runEitherIO :: (Ex.Exception e) => UIO (Either e a) -> IO a
#else
runEitherIO :: UIO (Either SomeNonPseudoException a) -> IO a
#endif
runEitherIO = either throwIO return <=< run
unsafeFromIO :: (Unexceptional m) => IO a -> m a
unsafeFromIO = lift . UIO
#ifdef __GLASGOW_HASKELL__
bracket :: (Unexceptional m) => UIO a -> (a -> UIO ()) -> (a -> UIO c) -> m c
bracket acquire release body =
unsafeFromIO $ Ex.bracket (run acquire) (run . release) (run . body)
#if MIN_VERSION_base(4,7,0)
forkFinally :: (Unexceptional m) => UIO a -> (Either PseudoException a -> UIO ()) -> m Concurrent.ThreadId
forkFinally body handler = unsafeFromIO $ Concurrent.forkFinally (run body) $ \result ->
case result of
Left e -> case Ex.fromException e of
Just pseudo -> run $ handler $ Left pseudo
Nothing -> error $ "Bug in UnexceptionalIO: forkFinally caught a non-PseudoException: " ++ show e
Right x -> run $ handler $ Right x
fork :: (Unexceptional m) => UIO () -> m Concurrent.ThreadId
fork body = do
parent <- unsafeFromIO Concurrent.myThreadId
forkFinally body $ either (handler parent) (const $ return ())
where
handler parent e
| Just Ex.ThreadKilled <- castException e = return ()
| Just (Ex.SomeAsyncException _) <- castException e =
unsafeFromIO $ Concurrent.throwTo parent e
| Just e <- castException e =
unsafeFromIO $ Concurrent.throwTo parent (e :: ExitCode)
| otherwise = unsafeFromIO $ Concurrent.throwTo parent (ChildThreadError e)
castException :: (Ex.Exception e1, Ex.Exception e2) => e1 -> Maybe e2
castException = Ex.fromException . Ex.toException
newtype ChildThreadError = ChildThreadError PseudoException deriving (Show, Typeable)
instance Ex.Exception ChildThreadError where
toException = Ex.asyncExceptionToException
fromException = Ex.asyncExceptionFromException
#endif
#endif