View source on Github

Overview

newtype ErrorT e m a = ErrorT { runErrorT :: m ( Either e a) } type MyStack = ErrorT MyError IO

type ErrorTUnwrapped e m a = m ( Either e a)

class MonadTrans t where lift :: Monad m => m a -> t m a

instance ( Error e) => MonadTrans ( ErrorT e) where lift m = ErrorT $ do a <- m return ( Right a)

sayHi :: IO () sayHi = putStrLn "Hello" sayHiError :: ErrorT MyError IO () sayHiError = lift $ putStrLn "Hello"

withMyFile :: ( Handle -> IO a) -> IO a withMyFile = withFile "test.txt" WriteMode sayHi :: Handle -> IO () sayHi handle = hPutStrLn handle "Hi there" useMyFile :: IO () useMyFile = withMyFile sayHi

sayHiError :: Handle -> ErrorT MyError IO () sayHiError handle = do lift $ hPutStrLn handle "Hi there, error!" throwError MyError

useMyFileErrorBad :: ErrorT MyError IO () useMyFileErrorBad = withMyFile sayHiError Couldn't match expected type ` ErrorT MyError IO ()' with actual type ` IO ()'

Intuition

useMyFileError1 :: ErrorT MyError IO () useMyFileError1 = let unwrapped :: Handle -> IO ( Either MyError ()) unwrapped handle = runErrorT $ sayHiError handle applied :: IO ( Either MyError ()) applied = withMyFile unwrapped rewrapped :: ErrorT MyError IO () rewrapped = ErrorT applied in rewrapped

Types

type Run t = forall n o b. ( Monad n, Monad o, Monad (t o)) => t n b -> n (t o b)

errorRun :: Run ( ErrorT MyError ) errorRun = undefined useMyFileError2 :: IO ( ErrorT MyError Identity ()) useMyFileError2 = let afterRun :: Handle -> IO ( ErrorT MyError Identity ()) afterRun handle = errorRun $ sayHiError handle applied :: IO ( ErrorT MyError Identity ()) applied = withMyFile afterRun in applied

MonadTransControl class MonadTrans t => MonadTransControl t where liftControl :: Monad m => ( Run t -> m a) -> t m a useMyFileError3 :: Monad m => ErrorT MyError IO ( ErrorT MyError m ()) useMyFileError3 = liftControl inside where inside :: Monad m => Run ( ErrorT MyError ) -> IO ( ErrorT MyError m ()) inside run = withMyFile $ helper run helper :: Monad m => Run ( ErrorT MyError ) -> Handle -> IO ( ErrorT MyError m ()) helper run handle = run (sayHiError handle :: ErrorT MyError IO ()) useMyFileError4 :: ErrorT MyError IO () useMyFileError4 = join useMyFileError3 And it turns out that this usage is so common, that Bas had mercy on us and defined a helper function: control :: ( Monad m, Monad (t m), MonadTransControl t) => ( Run t -> m (t m a)) -> t m a control = join . liftControl So all we need to write is: useMyFileError5 :: ErrorT MyError IO () useMyFileError5 = control inside where inside :: Monad m => Run ( ErrorT MyError ) -> IO ( ErrorT MyError m ()) inside run = withMyFile $ helper run helper :: Monad m => Run ( ErrorT MyError ) -> Handle -> IO ( ErrorT MyError m ()) helper run handle = run (sayHiError handle :: ErrorT MyError IO ()) useMyFileError6 :: ErrorT MyError IO () useMyFileError6 = control $ \run -> withMyFile $ run . sayHiError And it turns out that this usage is so common, that Bas had mercy on us and defined a helper function:So all we need to write is:

MonadControlIO type RunInBase m base = forall b. m b -> base (m b) Instead of dealing with a transformer, we're dealing with two monads. base is the underlying monad, and m is a stack built on top of it. RunInBase is a function that takes a value of the entire stack, pops out that base, and puts in on the outside. Unlike in the Run type, we don't replace it with an arbitrary monad, but with the original one. To use some more concrete types: RunInBase ( ErrorT MyError IO ) IO = forall b. ErrorT MyError IO b -> IO ( ErrorT MyError IO b) class MonadIO m => MonadControlIO m where liftControlIO :: ( RunInBase m IO -> IO a) -> m a controlIO :: MonadControlIO m => ( RunInBase m IO -> IO (m a)) -> m a controlIO = join . liftControlIO useMyFileError7 :: ErrorT MyError IO () useMyFileError7 = controlIO $ \run -> withMyFile $ run . sayHiError And as an advantage, it easily scales to multiple transformers: sayHiCrazy :: Handle -> ReaderT Int ( StateT Double ( ErrorT MyError IO )) () sayHiCrazy handle = liftIO $ hPutStrLn handle "Madness!" useMyFileCrazy :: ReaderT Int ( StateT Double ( ErrorT MyError IO )) () useMyFileCrazy = controlIO $ \run -> withMyFile $ run . sayHiCrazy Instead of dealing with a transformer, we're dealing with two monads. base is the underlying monad, and m is a stack built on top of it. RunInBase is a function that takes a value of the entire stack, pops out that base, and puts in on the outside. Unlike in the Run type, we don't replace it with an arbitrary monad, but with the original one. To use some more concrete types:And as an advantage, it easily scales to multiple transformers:

Real Life Examples

onException :: IO a -> IO b -> IO a

onExceptionError :: ErrorT MyError IO a -> ErrorT MyError IO b -> ErrorT MyError IO a onExceptionError action after = controlIO $ \run -> run action `onException` run after

allocaError :: ( Ptr Double -> ErrorT MyError IO b) -> ErrorT MyError IO b allocaError f = controlIO $ \run -> alloca $ run . f

Lost State

Note, any monadic side effects in m of the "release" computation will be discarded; it is run only for its side effects in IO.

More Complicated Cases

addMVarFinalizer :: MVar a -> IO () -> IO ()

addMVarFinalizerError :: MVar a -> ErrorT MyError IO () -> ErrorT MyError IO () addMVarFinalizerError mvar f = controlIO $ \run -> return $ liftIO $ addMVarFinalizer mvar (run f >> return ())

modifyMVar :: MVar a -> (a -> IO (a, b)) -> IO b