I've written the following monad transformers (which I believe are equivalent to each other), namely:
newtype MonadReaderT1 r m a = MonadReaderT (ReaderT (r m) m a)
newtype MonadReaderT2 r m a = MonadReaderT (ReaderT (m r) m a)
The purpose of these is that I basically want a ReaderT
, but my environment has to be accessed inside the Monad, it's not actually a fixed pure value (in my case, it's an auth token that needs to be periodically refreshed).
The reason why I think MonadReaderT1
and MonadReaderT2
are equivalent, because I can just go:
newtype B m = B (m A)
And then MonadReaderT1 B
is the same as MonadReaderT2 A
.
But I think I need this extra machinery here above and beyond what I get with plain old ReaderT
.
But I get the feeling I'm not the first person to have done this or needed this. Have I just reinvented an existing type, and if so what is it?
CodePudding user response:
I'm not sure it's worth defining a separate transformer in this case. Consider that if you were using a reader transformer with an Int
environment, you probably wouldn't feel a need to write a special transformer for this case:
newtype IntReaderT m a = IntReaderT (ReaderT Int m a)
Instead, you'd just use the ReaderT Int
directly.
Similarly, if you happen to want a monadic action as your environment, I think you should just use the ReaderT
directly, defining your monad stack as either a type:
type M = ReaderT (IO Token) IO
or a newtype:
newtype M a = M { unM :: ReaderT (IO Token) IO a }
along with a helper to fetch the token:
token :: M Token
token = ask >>= liftIO
If you prefer to write in a style that makes heavy use of mtl
constraints, you can instead define token
using a class and instance:
class MonadToken token m where
token :: m token
instance MonadToken Token M where
token = ask >>= liftIO
which allows you to write generic token-using monadic actions:
authorized :: (MonadToken token m) => m Bool
authorized = do
t <- token
...
without actually having to define a new transformer.