我有以下代码:
import Control.Monad
import Control.Monad.Trans
import Control.Monad.Trans.State
type T = StateT Int IO Int
someMaybe = Just 3
f :: T
f = do
x <- get
val <- lift $ do
val <- someMaybe
-- more code in Maybe monad
-- return 4
return 3
当我使用do
符号在Maybe
monad中工作时,它失败了。从它给出的错误看起来这个do
的类型签名不匹配。但是我不知道如何解决它。我尝试了一些lift
组合,但它们都没有用,我不想再猜了。
答案 0 :(得分:8)
问题是Maybe
不是变压器堆栈的一部分。如果您的变压器只知道StateT Int
和IO
,则它不知道如何解除Maybe
。
您可以通过将类型T
更改为以下内容来解决此问题:
type T = StateT Int (MaybeT IO) Int
(您需要导入Control.Monad.Trans.Maybe
。)
您还需要更改内部do
以使用MaybeT
而不是Maybe
。这意味着使用Maybe a
包装原始MaybeT . return
值:
f :: T
f = do
x <- get
val <- lift $ do
val <- MaybeT $ return someMaybe
-- more code in Maybe monad
return 4
return 3
这有点尴尬,所以你可能想写一个像liftMaybe
这样的函数:
liftMaybe = MaybeT . return
如果您在代码的其他部分使用lift
提升IO a
值,则现在会中断,因为您现在在变换器堆栈中有三个级别。您将收到如下错误:
Couldn't match expected type `MaybeT IO t0'
with actual type `IO String'
要解决此问题,您应该对所有原始liftIO
值使用IO a
。这通过任意数量的变换器层使用类型类到生命IO
动作。
回复你的评论:如果你只有一些代码取决于Maybe
,那么将do
符号的结果放入变量并与之匹配会更容易:
let maybeVal = do val <- someMaybe
-- more Maybe code
return 4
case maybeVal of
Just res -> ...
Nothing -> ...
这意味着Maybe
代码将无法执行IO。您也可以自然地使用fromMaybe
之类的功能代替case
。
答案 1 :(得分:3)
如果您想纯粹在do
monad中运行内部Maybe
中的代码,则无法访问StateT Int
或IO
monad(可能是一件好事)。这样做会返回Maybe
值,您必须仔细检查:
import Control.Monad
import Control.Monad.Trans
import Control.Monad.Trans.State
type T = StateT Int IO Int
someMaybe = Just 3
f :: T
f = do
x <- get
-- no need to use bind
let mval = do
-- this code is purely in the Maybe monad
val <- someMaybe
-- more code in Maybe monad
return 4
-- scrutinize the resulting Maybe value now we are back in the StateT monad
case mval of
Just val -> liftIO . putStrLn $ "I got " ++ show val
Nothing -> liftIO . putStrLn $ "I got a rock"
return 3