也许monad里面有一堆变形金刚

时间:2013-04-17 15:28:54

标签: haskell monads

我有以下代码:

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组合,但它们都没有用,我不想再猜了。

2 个答案:

答案 0 :(得分:8)

问题是Maybe不是变压器堆栈的一部分。如果您的变压器只知道StateT IntIO,则它不知道如何解除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 IntIO 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