Reputation: 2799
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?
Upvotes: 5
Views: 584
Reputation: 64750
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
).
Upvotes: 6
Reputation: 3589
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!
Upvotes: 3