Working in a Coding Dojo today I tried the following
example :: IO ()
example = do input <- getLine
parsed <- parseOnly parser input
...
where parseOnly :: Parser a -> Either String a
(from attoparsec
) of course the compiler complained that Either ..
is not IO ..
essentially telling me I am mixing monads.
Of course this can be solved by
case parseOnly parser input of .. -> ..
which is kind of unelegant, I think. Also my guess is that somebody else had this problem earlier and the solution I think is related to monad transformers, but the last bits I cannot piece together.
It also reminded me of liftIO
- but this is the other way around I think which solves the problem of lifting an IO action happening inside some surrounding monad (more precisely MonadIO
- say for example inside Snap
when one wants to print something to stdout
while getting some http).
More general this problem seems to be for a Monad m1
and a (different) Monad m2
how can I do something like
example = do a <- m1Action
b <- m2Action
..
You can't, in general. The whole do block has to be one specific monad (because
example
needs to have some specific type). If you could bind an arbitrary other monad inside that do block, you'd haveunsafePerformIO
.Monad transformers allow you to produce one monad combining the kinds of things multiple other monads can do. But you have to decide that all the actions in your do block use the same monad transformer stack to use those, they're not a way to arbitrarily switch monads mid do-block.
Your solution with
case
only works because you've got a particular known monad (Either) that has a way of extracting values from inside it. Not all monads provide this, so it's impossible to build a general solution without knowing the particular monads involved. That's why the do block syntax doesn't provide such a shortcut.You need to make that expression type check; just as in pure code. Here,
de-sugars to:
>>=
is of signature:the outer bind is specialized with
m1
, so it expects that the expression inside parenthesis be of type:a -> m1 b
, whereas the inner bind is specialized withm2
, so the expression inside parenthesis will be of typea -> m2 b
:for this to type check, you need a function of signature
m2 b -> m1 b
in between the two; that is whatlift
does for a certain class ofm2
andm1
monads: namelym1 ~ t m2
wheret
is an instance ofMonadTrans
:In general, monad transformers are for this kind of interleaving. You can use
ExceptT
Note that
parseOnly
must returnExceptT String IO a
for somea
. Or betterExceptT String m a
for anym
. Or if you wantparseOnly
to returnEither String a
it'sBut I think all you need is just