在其他一些 monad m1 中绑定一个 monadic 值 (m2 a)

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

今天在 Coding Dojo 工作,我尝试了以下方法

example :: IO ()
example = do input <- getLine
             parsed <- parseOnly parser input
             ...

其中 parseOnly :: Parser a -> Either String a(来自 attoparsec)当然编译器抱怨说 Either .. 不是 IO .. 本质上告诉我我正在混合 monads。

当然可以通过

解决
             case parseOnly parser input of .. -> ..

我认为这有点不雅。另外我的猜测是其他人早些时候遇到过这个问题,我认为解决方案与 monad 转换器有关,但最后一点我无法拼凑起来。

它也让我想起了 liftIO - 但这是我认为的另一种方式,它解决了解除发生在周围一些 monad 中的 IO 操作的问题(更准确地说 MonadIO - 比如Snap 中的示例,当一个人想要在获取一些 http 的同时将某些内容打印到 stdout 时。

更普遍的是,这个问题似乎是针对 Monad m1 和(不同的)Monad m2 我怎样才能做类似

example = do a <- m1Action
             b <- m2Action
             ..

一般情况下,您不能。整个 do 块必须是一个特定的 monad(因为 example 需要有一些特定的类型)。如果您可以在该 do 块内绑定任意其他 monad,您将拥有 unsafePerformIO.

Monad 转换器允许您生成一个 monad,结合多个其他 monad 可以做的事情。但是你必须决定 all 你的 do 块中的动作使用相同的 monad 转换器堆栈来使用它们,它们不是在 do-block 中任意切换 monad 的方法。

您使用 case 的解决方案之所以有效,是因为您有一个特定的已知 monad(任一),它可以从其中提取值。并非所有 monad 都提供此功能,因此在不了解所涉及的特定 monad 的情况下不可能构建通用解决方案。这就是为什么 do 块语法不提供这样的快捷方式。

您需要对该表达式进行 类型检查;就像在纯代码中一样。这里,

... = do a <- act1  -- m1 monad
         b <- act2  -- m2 monad
         ...

de-sugars 至:

... = act1 >>= (\a -> act2 >>= \b -> ...)

>>=签名:

(>>=) :: Monad m => m a -> (a -> m b) -> m b

外部 bind 专用于 m1,因此它期望括号内的表达式是类型:a -> m1 b,而内部绑定是专用于 m2,因此括号内的表达式将是 a -> m2 b:

类型
-- outer bind expects (    \a   ->   m1 b      )
             act1 >>= (\a -> act2 >>= \b -> ...)
-- inner bind results (    \a   ->   m2 b      )

为了类型检查,你需要一个介于两者之间的签名函数m2 b -> m1 b;这就是 liftm2m1 单子的 某些 class 所做的:即 m1 ~ t m2 其中 tMonadTrans:

的实例
lift :: (Monad m, MonadTrans t) => m a -> t m a

一般来说,monad transformer就是为了这种交错。您可以使用 ExceptT

example :: IO (Either String ())
example = runExceptT $ do
    input <- liftIO getLine
    parsed <- parseOnly parser input
    ...

请注意,对于某些 aparseOnly 必须 return ExceptT String IO a。或者更好的 ExceptT String m a 对于任何 m。或者如果你想要 parseOnly 到 return Either String a 它是

example :: IO (Either String ())
example = runExceptT $ do
    input <- lift getLine
    parsed <- ExceptT $ return $ parseOnly parser input
    ...

但我认为你只需要

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