问题
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
..
回答1:
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 have unsafePerformIO
.
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.
回答2:
In general, monad transformers are for this kind of interleaving. You can use ExceptT
example :: IO (Either String ())
example = runExceptT $ do
input <- liftIO getLine
parsed <- parseOnly parser input
...
Note that parseOnly
must return ExceptT String IO a
for some a
. Or better ExceptT String m a
for any m
. Or if you want parseOnly
to return Either String a
it's
example :: IO (Either String ())
example = runExceptT $ do
input <- lift getLine
parsed <- ExceptT $ return $ parseOnly parser input
...
But I think all you need is just
eitherToIO :: Either String a -> IO a
eitherToIO (Left s) = error s
eitherToIO (Right x) = return x
parseOnly :: ... -> String -> Either String Int
example :: IO ()
example = do
input <- getLine
parsed <- eitherToIO $ parseOnly parser input
...
回答3:
You need to make that expression type check; just as in pure code. Here,
... = do a <- act1 -- m1 monad
b <- act2 -- m2 monad
...
de-sugars to:
... = act1 >>= (\a -> act2 >>= \b -> ...)
>>=
is of signature:
(>>=) :: Monad m => m a -> (a -> m b) -> m b
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 with m2
, so the expression inside parenthesis will be of type a -> m2 b
:
-- outer bind expects ( \a -> m1 b )
act1 >>= (\a -> act2 >>= \b -> ...)
-- inner bind results ( \a -> m2 b )
for this to type check, you need a function of signature m2 b -> m1 b
in between the two; that is what lift does for a certain class of m2
and m1
monads: namely m1 ~ t m2
where t
is an instance of MonadTrans
:
lift :: (Monad m, MonadTrans t) => m a -> t m a
来源:https://stackoverflow.com/questions/34912922/bind-a-monadic-value-m2-a-inside-some-other-monad-m1