4
votes

I was trying to write my own monad transformers where it would make sense to have multiple of the same monad transformer on the stack with different types. The issue can be illustrated with the reader monad.

The reader monad is offered as a way to hold a read only context of a given type

ex1 :: Reader Bool Bool
ex1 = ask

or

ex2 :: Reader Char Bool
ex2 = pure True

monad transformers allow less restrictive assumptions about the underlining monad

ex3 :: (MonadReader Bool m) => m Bool
ex3 = ask

However, what if I want to have more than 1 read only environment? I can write a function like

ex4 :: (MonadReader Bool m, MonadReader Char m) => m Bool
ex4 = ask

However, as far as I can tell, there is no way to run ex4 since

class Monad m => MonadReader r m | m -> r 

means that each MonadReader has a unique reading type. Is there a standard work around for multiple transformers on the same stack? Should I try to avoid this entirely?

2
Doesn't lift ask work?arrowd
@arrowd the type signature is itself illegal. The MonadReader type class has a functional dependency that says that the monad m must uniquely determine the type that is being read. It does work if you use an explicit monad transformer stack instead of the type class thoughsara
Keep in mind MonadReader is not a transformer. If you use a transformer, a la the transformers package, and avoid thinking in the terms provided by mtl then you'll do fine.Thomas M. DuBuisson

2 Answers

4
votes

Use a transformer and lift to get to your inner monad:

import Control.Monad.Trans.Reader
import Control.Monad.Trans.Class (lift)

type MyMonad a = ReaderT Bool (Reader Char) a

askBool :: MyMonad Bool
askBool = ask
askChar :: MyMonad Char
askChar = lift ask

The code you presented didn't use any monad transformer (directly). It used the reader monad (which happens to be a transformer applied to the identity monad) and the MonadReader type class. As you noticed, the type function implied by MonadReader can't result in two different outputs (the environment types) for the same input (the monad m).

2
votes

One way to deal with it in a relatively straightforward way is to create a type that represents the state you wanna keep track of. Say you want to keep track of both a Bool and a Char as in your example

data MyState = MyState { getBool :: Bool, getChar :: Char }

f :: MonadReader MyState m => m Bool
f = asks getBool

Others may have more advanced solutions!