6

我有以下代码:

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符号在Maybemonad 中工作时,它失败了。从它给出的错误来看,它的类型签名do似乎不匹配。但是我不知道如何解决它。我尝试了一些lift组合,但没有一个有效,我不想再猜测了。

4

2 回答 2

8

问题是它Maybe不是您的变压器堆栈的一部分。如果你的变压器只知道StateT IntIO,它不知道如何抬起Maybe

您可以通过将类型更改T为以下内容来解决此问题:

type T = StateT Int (MaybeT IO) Int

(您需要导入Control.Monad.Trans.Maybe。)

你还需要改变你的内在do来工作MaybeT而不是Maybe。这意味着用 包装原始Maybe aMaybeT . 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.

于 2013-04-17T15:35:52.827 回答
3

如果您想do纯粹在Maybemonad 中运行内部代码,您将无法访问StateT Intor IOmonad(这可能是一件好事)。这样做会返回一个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
于 2013-04-17T15:55:46.137 回答