bind a monadic value (m2 a) inside some other monad m1

匆匆过客 提交于 2019-12-04 15:48:44
Ben

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.

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

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
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!