今天在编码道场工作,我尝试了以下方法
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
...
请注意,对于某些a
,parseOnly
必须返回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
对某类m2
和m1
monads所做的:即m1 ~ t m2
其中t
是MonadTrans
的实例:
lift :: (Monad m, MonadTrans t) => m a -> t m a