{- | This module redefines some of the functions in "Control.Exception" to work for more general monads built on top of 'IO'. -} module System.Console.Haskeline.MonadException( -- * The MonadException class MonadException(..), -- * Generalizations of Control.Exception catch, handle, finally, throwIO, throwTo, bracket, -- * Helpers for defining \"wrapper\" functions liftIOOp, liftIOOp_, -- * Internal implementation RunIO(..), -- * Extensible Exceptions Exception, SomeException(..), E.IOException(), ) where import qualified Control.Exception as E import Control.Exception (Exception,SomeException) #if __GLASGOW_HASKELL__ < 705 import Prelude hiding (catch) #endif import Control.Monad(liftM, join) import Control.Monad.IO.Class import Control.Monad.Trans.Reader import Control.Monad.Trans.State.Strict import Control.Monad.Trans.Error import Control.Monad.Trans.List import Control.Monad.Trans.Maybe import Control.Monad.Trans.RWS import Control.Monad.Trans.Writer import Data.Monoid import Control.Concurrent(ThreadId) -- This approach is based on that of the monad-control package. -- Since we want to use haskeline to bootstrap GHC, we reimplement -- a simplified version here. -- Additionally, we avoid TypeFamilies (which are used in the latest version of -- monad-control) so that we're still compatible with older versions of GHC. -- | A 'RunIO' function takes a monadic action @m@ as input, -- and outputs an IO action which performs the underlying impure part of @m@ -- and returns the ''pure'' part of @m@. -- -- Note that @(RunIO return)@ is an incorrect implementation, since it does not -- separate the pure and impure parts of the monadic action. This module defines -- implementations for several common monad transformers. newtype RunIO m = RunIO (forall b . m b -> IO (m b)) -- Uses a newtype so we don't need RankNTypes. -- | An instance of 'MonadException' is generally made up of monad transformers -- layered on top of the IO monad. -- -- The 'controlIO' method enables us to \"lift\" a function that manages IO actions (such -- as 'bracket' or 'catch') into a function that wraps arbitrary monadic actions. class MonadIO m => MonadException m where controlIO :: (RunIO m -> IO (m a)) -> m a -- | Lift a IO operation -- -- > wrap :: (a -> IO b) -> IO b -- -- to a more general monadic operation -- -- > liftIOOp wrap :: MonadException m => (a -> m b) -> m b -- -- For example: -- -- @ -- 'liftIOOp' ('System.IO.withFile' f m) :: MonadException m => (Handle -> m r) -> m r -- 'liftIOOp' 'Foreign.Marshal.Alloc.alloca' :: (MonadException m, Storable a) => (Ptr a -> m b) -> m b -- 'liftIOOp' (`Foreign.ForeignPtr.withForeignPtr` fp) :: MonadException m => (Ptr a -> m b) -> m b -- @ liftIOOp :: MonadException m => ((a -> IO (m b)) -> IO (m c)) -> (a -> m b) -> m c liftIOOp f g = controlIO $ \(RunIO run) -> f (run . g) -- | Lift an IO operation -- -- > wrap :: IO a -> IO a -- -- to a more general monadic operation -- -- > liftIOOp_ wrap :: MonadException m => m a -> m a liftIOOp_ :: MonadException m => (IO (m a) -> IO (m a)) -> m a -> m a liftIOOp_ f act = controlIO $ \(RunIO run) -> f (run act) catch :: (MonadException m, E.Exception e) => m a -> (e -> m a) -> m a catch act handler = controlIO $ \(RunIO run) -> E.catch (run act) (run . handler) handle :: (MonadException m, Exception e) => (e -> m a) -> m a -> m a handle = flip catch bracket :: MonadException m => m a -> (a -> m b) -> (a -> m c) -> m c bracket before after thing = controlIO $ \(RunIO run) -> E.bracket (run before) (\m -> run (m >>= after)) (\m -> run (m >>= thing)) finally :: MonadException m => m a -> m b -> m a finally thing ender = controlIO $ \(RunIO run) -> E.finally (run thing) (run ender) throwIO :: (MonadIO m, Exception e) => e -> m a throwIO = liftIO . E.throwIO throwTo :: (MonadIO m, Exception e) => ThreadId -> e -> m () throwTo tid = liftIO . E.throwTo tid ---------- -- Instances of MonadException. -- Since implementations of this class are non-obvious to a casual user, -- we provide instances for nearly everything in the transformers package. instance MonadException IO where controlIO f = join $ f (RunIO (liftM return)) -- Note: it's crucial that we use "liftM return" instead of "return" here. -- For example, in "finally thing end", this ensures that "end" will always run, -- regardless of whether an mzero occurred inside of "thing". instance MonadException m => MonadException (ReaderT r m) where controlIO f = ReaderT $ \r -> controlIO $ \(RunIO run) -> let run' = RunIO (fmap (ReaderT . const) . run . flip runReaderT r) in fmap (flip runReaderT r) $ f run' instance MonadException m => MonadException (StateT s m) where controlIO f = StateT $ \s -> controlIO $ \(RunIO run) -> let run' = RunIO (fmap (StateT . const) . run . flip runStateT s) in fmap (flip runStateT s) $ f run' instance MonadException m => MonadException (MaybeT m) where controlIO f = MaybeT $ controlIO $ \(RunIO run) -> let run' = RunIO (fmap MaybeT . run . runMaybeT) in fmap runMaybeT $ f run' instance (MonadException m, Error e) => MonadException (ErrorT e m) where controlIO f = ErrorT $ controlIO $ \(RunIO run) -> let run' = RunIO (fmap ErrorT . run . runErrorT) in fmap runErrorT $ f run' instance MonadException m => MonadException (ListT m) where controlIO f = ListT $ controlIO $ \(RunIO run) -> let run' = RunIO (fmap ListT . run . runListT) in fmap runListT $ f run' instance (Monoid w, MonadException m) => MonadException (WriterT w m) where controlIO f = WriterT $ controlIO $ \(RunIO run) -> let run' = RunIO (fmap WriterT . run . runWriterT) in fmap runWriterT $ f run' instance (Monoid w, MonadException m) => MonadException (RWST r w s m) where controlIO f = RWST $ \r s -> controlIO $ \(RunIO run) -> let run' = RunIO (fmap (\act -> RWST (\_ _ -> act)) . run . (\m -> runRWST m r s)) in fmap (\m -> runRWST m r s) $ f run'