将一元值 (M2 A) 绑定到其他某个单子 M1 中



今天在编码道场工作,我尝试了以下方法

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

attoparsec parseOnly :: Parser a -> Either String a当然,编译器抱怨Either ..不是IO ..本质上告诉我我在混合monads。

当然这可以通过

             case parseOnly parser input of .. -> ..

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

这也让我想起了liftIO - 但我认为这是相反的方式,它解决了在周围的一些 monad 内发生的 IO 操作的问题(更准确地说是MonadIO - 例如在 Snap 内部,当一个人想要打印一些东西时stdout同时获得一些 http)。

更普遍的这个问题似乎适用于Monad m1和(不同的)Monad m2我该如何做类似的事情

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

一般来说,你不能。整个 do 块必须是一个特定的 monad(因为example需要具有某种特定的类型)。如果你可以在该 do 块中绑定任意其他 monad,你就会unsafePerformIO .

子变压器允许您产生一个单子,结合多个其他单子可以做的事情。但是你必须决定你的do块中的所有动作都使用相同的monad转换器堆栈来使用这些,它们不是一种任意切换monad

中间do块的方法。

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

一般来说,单体变压器就是为了这种交错。您可以使用ExceptT

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

请注意,对于某些aparseOnly必须返回ExceptT String IO a。或者对任何m都有更好的ExceptT String m a.或者,如果您想parseOnly返回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
    ...

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

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

脱糖以:

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

>>=签名:

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

绑定专用于 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在两者之间;这就是lift某类m2m1 monads所做的:即m1 ~ t m2 其中tMonadTrans的实例:

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

最新更新