IO transformerIt has been
claimed
that there is no IO transformer in Haskell. This seems overly
pessimistic. The obvious approach is to use
newtype IOT m a = IOT (m (IO a))
However, we get stuck when we try to define
join :: Monad m => m (IO (m (IO a))) -> m (IO a)
The typical approach would be to try to commute the IO (m ...) to m (IO ...) but there’s just no way to do that. We can’t even do it for
some simple choices of m like Maybe let alone uniformly for all
Monads m.
But there’s a another way. If we can turn all the ms into IO then
we can just join them all together into one IO action. This
suggests an IO transformer like
newtype IOT m a = IOT ((forall a. m a -> IO a) -> IO a)
In fact, it’s just a use of the Reader transformer.
newtype Morph f g = Morph { runMorph :: forall a. (f a -> g a) }
newtype IOT m a = IOT { runIOT :: ReaderT (Morph m IO) IO a }
Using ReaderT here is just a convenience that means we automatically
get a Monad instance cheaply. It doesn’t play a role of any substance.
instance Monad (IOT m) where
return = IOT . return
m >>= f = IOT (runIOT m >>= (runIOT . f))
The MonadTrans instance is
instance MonadTrans IOT where
lift act = IOT $ do
f <- ask
lift (runMorph f act)
That is, we just convert all the m actions into IO actions. (Note
that the latter lift is in ReaderT). Does it satisfy the
MonadTrans
laws?
Well (eliding runMorph and IOT wrapping/unwrapping for notational
convenience),
lift (return x) = do
f <- ask
lift (f (return x))
which is
lift (return x) = do
f <- ask
return x
in the case where f (return x) = return x, i.e. lift . return = return. Additionally
lift (m >>= g) = do
f <- ask
lift (f (m >>= g))
which is
lift (m >>= g) = do
f <- ask
x <- lift (f m)
f' <- ask
lift (f' (g x))
i.e.
lift m >>= \x -> lift (g x)
that is
lift m >>= (lift . g)
in the case where f (m >>= g) = f m >>= (f . g). So lift (m >>= g) = lift m >>= (lift . g).
Thus we see that IOT is a monad transformer if the reader
environment is a monad morphism f satisfying
f (return x) = return xf (m >>= g) = f m >>= (f . g)IOTYou can run an IOT m action by providing a way of interpreting an
m action in IO. (Remember the interpretation has to be a monad
morphism for the monad laws to hold).
runIOT' :: (forall a. m a -> IO a) -> IOT m a -> IO a
runIOT' f m = runReaderT (runIOT m) (Morph f)
It seems that there are several uses of this transformer.
MaybeFor m = Maybe we can throw an error for Nothing.
handleMaybe :: Maybe a -> IO a
handleMaybe Nothing = error "Nothing"
handleMaybe (Just a) = return a
maybeExample :: IO ()
maybeExample = runIOT' handleMaybe $ do
liftIO (putStrLn "Hello")
lift Nothing
liftIO (putStrLn "Goodbye")
> maybeExample
Hello
*** Exception: Nothing
EitherFor m = Either e we can throw an error which depends on the
Left value.
handleEither :: Show e => Either e a -> IO a
handleEither (Left e) = error (show e)
handleEither (Right a) = return a
eitherExample :: IO ()
eitherExample = runIOT' handleEither $ do
liftIO (putStrLn "Hello")
lift (Left 1234)
liftIO (putStrLn "Goodbye")
> eitherExample
Hello
*** Exception: 1234
WriterFor m = Writer [w] we can print a log message.
handleWriter :: Show w => Writer [w] a -> IO a
handleWriter m = mapM_ print ws >> return a
where (a, ws) = runWriter m
writerExample :: IO ()
writerExample = runIOT' handleWriter $ do
liftIO (putStrLn "Hello")
lift (tell ["log1", "log2"])
liftIO (putStrLn "Goodbye")
lift (tell ["log3"])
> writerExample
Hello
"log1"
"log2"
Goodbye
"log3"
State sFor m = State s we can use an IORef to hold the state.
stateExample :: IO ()
stateExample = do
ref <- newIORef 0
let handleState :: State Integer a -> IO a
handleState m = do
s <- readIORef ref
let (a, s') = runState m s
writeIORef ref s'
return a
runIOT' handleState $ do
x <- lift get
liftIO (print x)
lift (modify (+1))
y <- lift get
liftIO (print y)
> stateExample
0
1
IO transformer?Firstly note that IOT Maybe, IOT (Either e) and IOT (State s)
seem to give strictly less power than MaybeT IO, EitherT e IO and
StateT s IO respectively. IOT (Writer [w]) does seem to be
different to WriterT [w] IO in that it preserves the ordering of
IO and Writer actions.
But is there some definition for “M transformer”, for an arbitrary
monad M? And what is the justification for saying that Haskell has
no IO transformer? Even though it may not be especially useful,
this IOT seems like a reasonable candidate for one.
{-# LANGUAGE Rank2Types #-}
import Control.Monad.Trans.Reader
import Control.Monad.Trans.Writer
import Control.Monad.Trans.State
import Control.Monad.Trans.Class
import Data.IORef
newtype Morph f g = Morph { runMorph :: forall a. (f a -> g a) }
newtype IOT m a = IOT { runIOT :: ReaderT (Morph m IO) IO a }
instance Monad (IOT m) where
return = IOT . return
m >>= f = IOT (runIOT m >>= (runIOT . f))
instance MonadTrans IOT where
lift act = IOT $ do
f <- ask
lift (runMorph f act)
liftIO :: IO a -> IOT m a
liftIO = IOT . lift
handleMaybe :: Maybe a -> IO a
handleMaybe Nothing = error "Nothing"
handleMaybe (Just a) = return a
runIOT' :: (forall a. m a -> IO a) -> IOT m a -> IO a
runIOT' f m = runReaderT (runIOT m) (Morph f)
maybeExample :: IO ()
maybeExample = runIOT' handleMaybe $ do
liftIO (putStrLn "Hello")
lift Nothing
liftIO (putStrLn "Goodbye")
handleEither :: Show e => Either e a -> IO a
handleEither (Left e) = error (show e)
handleEither (Right a) = return a
eitherExample :: IO ()
eitherExample = runIOT' handleEither $ do
liftIO (putStrLn "Hello")
lift (Left 1234)
liftIO (putStrLn "Goodbye")
handleWriter :: Show w => Writer [w] a -> IO a
handleWriter m = mapM_ print ws >> return a
where (a, ws) = runWriter m
writerExample :: IO ()
writerExample = runIOT' handleWriter $ do
liftIO (putStrLn "Hello")
lift (tell ["log1", "log2"])
liftIO (putStrLn "Goodbye")
lift (tell ["log3"])
stateExample :: IO ()
stateExample = do
ref <- newIORef 0
let handleState :: State Integer a -> IO a
handleState m = do
s <- readIORef ref
let (a, s') = runState m s
writeIORef ref s'
return a
runIOT' handleState $ do
x <- lift get
liftIO (print x)
lift (modify (+1))
y <- lift get