bind a monadic value (m2 a) inside some other mona

2019-04-16 12:31发布

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
             ..

3条回答
淡お忘
2楼-- · 2019-04-16 13:16

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.

查看更多
何必那么认真
3楼-- · 2019-04-16 13:25

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
查看更多
淡お忘
4楼-- · 2019-04-16 13:29

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
    ...
查看更多
登录 后发表回答