如何将两个ErrorT monad变换器叠加在一起?

chi*_*ro2 2 monads haskell monad-transformers

假设我有这两个功能:

errorm :: ( MonadError String m ) => Bool ->  m Int
errorm cond = if cond then return 1 else throwError "this is an error"

errorms :: ( MonadError String m ) => Bool ->  m String
errorms cond = if cond then return "works" else throwError "does not work"
Run Code Online (Sandbox Code Playgroud)

如您所见,一个在安全案例中返回一个字符串,而另一个返回一个int

我现在想在另一个monad中一起使用它们.平凡:

errErr :: MonadError String m => Bool -> Bool -> m (Int, String)
errErr b1 b2 = do
    a <- errorm b1 
    b <- errorms b2
    return (a,b)
Run Code Online (Sandbox Code Playgroud)

这里的函数签名是由GHC派生的,我不知道如何使用这个函数.我试过这个:

runErrorT ( runErrorT ( errErr1 True True ) )  -- should give me (Right 1, Right "works")
Run Code Online (Sandbox Code Playgroud)

但相反,它给了我:

Ambiguous type variable `e0' in the constraint:
(Error e0) arising from a use of `errErr1'
Probable fix: add a type signature that fixes these type variable(s)
In the first argument of `runErrorT', namely `(errErr1 True True)'
In the first argument of `runErrorT', namely
  `(runErrorT (errErr1 True True))'
In the expression: runErrorT (runErrorT (errErr1 True True))
Run Code Online (Sandbox Code Playgroud)

一般来说,这只是我问题的一个例子.我觉得我没有理解如何准确地堆叠两个具有相同类别但具有不同类型参数的monadT.另一个例子可能是堆叠这对函数:

f :: ( MonadState Int m ) => m ()
g :: ( MonadState String m ) => m ()
Run Code Online (Sandbox Code Playgroud)

-------------------------------------------------- - 更新----------------------------------------------- -----

根据下面的Daniel的评论,我从上面添加了函数f和g的具体实例.但感谢Tikhon的回答,我想我已经明白了.

type Counter = Int
type Msg     = String

incr :: (MonadState Counter m) => Counter -> m ()
incr i = modify (+i)

addMsg :: ( MonadState Msg m ) => Msg -> m()
addMsg msg = modify ( ++ msg )

incrMsg:: (MonadTrans t, MonadState Msg m, MonadState Counter (t m)) => t m ()
incrMsg = do 
    lift . addMsg $ "one"
    incr 1
    return ()

incrMsgt = runIdentity $ runStateT ( runStateT incrMsg 1 ) "hello" :: (((), Int), String)
Run Code Online (Sandbox Code Playgroud)

Tik*_*vis 5

在这种特殊情况下,你就不会需要堆两个变压器-因为这两者MonadError String,它们可以被用来作为同一单子.您可以使用errormerrorms在一起,就像你在任何其他单子使用两个值.

作为一个更具体的解释,忽略变换器一秒钟:你可以想象这些值只是Either String IntEither String String.显然,你可以一起使用它们.这就是为什么你只需要一个runErrorT而不是两个:两个值都在同一个monad中.

现在,你的实际问题是:你如何堆叠两个monad变换器?它就像组合任何两个monad变换器一样.彼此堆叠的两个状态变压器看起来就像两个不同的变压器堆叠在一起.

现在,使用它们有点棘手.根据您使用的是哪一种,您需要使用lift不同的方式.如果您在基本monad中有值,则需要提升两次.如果内部状态monad中有值,则需要使用一次.如果你有一个在外层,你根本不需要它.这就像普通的变压器一样.

让我们回到你的错误例子,假设你居然要叠加两个不同的错误单子变压器替换使用它们为一体.这意味着如果你想在内部错误中抛出一个错误,你就必须写lift (throwError "message").如果你实际上已经完成了这个并且有两个堆叠的错误变换器,那么使用runErrorT两次就会有效.