user833970
user833970

Reputation: 2799

monad stack with multiple of the same transformer

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

Answers (2)

Thomas M. DuBuisson
Thomas M. DuBuisson

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

sara
sara

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

Related Questions