2

Let's say I have these two functions:

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"

As you can see, one returns a string in the safe case, while the other returns an int

I now want to use them together within another monad. Trivially:

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

The function signature here is derived by the GHC, and I am not sure how to use this function. I tried this:

runErrorT ( runErrorT ( errErr1 True True ) )  -- should give me (Right 1, Right "works")

But instead it gives me:

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))

In general, this is just one instance of my problem. I feel like I am not grasping how exactly to stack two monadT that are of the same class, but have different type parameters. Another example might be stacking the pair of a functions:

f :: ( MonadState Int m ) => m ()
g :: ( MonadState String m ) => m ()

---------------------------------------------------- Update ----------------------------------------------------

Per Daniel's comment below, I added a concrete instance of functions f and g from above. But thanks to Tikhon's answer, I think I figured it out.

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)
4

1 回答 1

5

在这种特殊情况下,您不需要堆叠两个变压器——因为两者都是MonadError String,它们可以用作同一个monad。你可以一起使用errormerrorms,就像在任何其他 monad 中使用两个值一样。

作为更具体的解释,暂时忽略转换器:您可以想象这些值是Either String IntEither String String。显然,您可以将它们一起使用。这就是为什么runErrorT最后只需要一个而不是两个的原因:两个值都在同一个 monad 中。

现在,您的实际问题如何:如何堆叠两个单子变压器?它就像组合任何两个 monad 转换器一样工作。两个相互堆叠的状态变压器看起来就像两个不同的变压器相互堆叠。

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

回到您的错误示例,假设您实际上确实想要堆叠两个不同的错误单子转换器,而不是将它们用作一个。这意味着,如果您想在内部错误中抛出错误,则必须编写lift (throwError "message"). 如果您确实这样做了并且有两个堆叠的误差转换器,那么使用runErrorT两次就可以了。

于 2013-04-28T20:09:05.443 回答