2
votes

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)
1
You need just one runErrorT, both, errorm and errorms happily run in the same monad: runErrorT ( errErr True True ) :: [Either String (Int,String)] produces [Right (1,"works")].Daniel Fischer
In your example, both have MonadError String m constraints, and only the result type parameter is different. You don't need any stacking for that. In the general case alluded to at the bottom, you do, because there the state type parameter is different. Can you clarify what problem exactly you are trying to solve?Daniel Fischer
Thanks for the clarification, I didn't understand the problem well enough that's why I accidentally kind of asked two questions in one.xiaolingxiao

1 Answers

5
votes

In this particular case, you do not need to stack two transformers--since both are MonadError String, they can be used as the same monad. You can use errorm and errorms together just like you would use two values in any other monad.

As a more concrete explanation, ignore the transformers for a second: you can imagine that the values are just Either String Int and Either String String. Clearly, you can just use them together. This is why you only need one runErrorT at the end rather than two: both values are in the same monad.

Now, what about your actual question: how do you stack two monad transformers? It works just like combining any two monad transformers. Two state transformers stacked upon each other look just like two different transformers stacked upon each other.

Now, it is a little tricky to use them. Depending on which one you're using, you will need to use lift differently. If you have a value in the base monad, you need to lift twice. If you have a value in the inner state monad, you will need to use it once. And if you have one in the outer level, you won't need it at all. This is just like normal transformers.

Going back to your error example, let's imagine you actually did want to stack two different error monad transformers instead of using them as one. This would mean that if you want to throw an error in the inner one, you would have to write lift (throwError "message"). If you had actually done this and had two stacked error transformers, than using runErrorT twice would have worked.