Struggle to mtl types

Hi, everyone, I’ve finally reached transformers and mtl, still studying it and, as I think, I grasped the main idea. However, I don’t understand types of mtl functions.
Let me explain what I mean

For me, it’s easier to understand Haskell if I write type for each function, for example

instance Functor m => Functor (ReaderT r m) where
  fmap :: (a -> b) -> ReaderT r m a -> ReaderT r m b
  fmap f m = ReaderT $ \ r -> fmap f $ runReaderT m r

But, as I said, I don’t understand how to apply that to mtl functions
For example:

class Monad m => MonadReader r m | m -> r where
  ask :: m r  -- < for this one, it's simple, I have to substitute what will be in m

instance Monad m => MonadReader r (ReaderT r m) where
  ask :: ReaderT r m r  --< and it typechecks, everything is right. cool!
  ask = ReaderT $ \ r -> return r

implementation MonadReader for StateT is the following one

instance MonadReader r m => MonadReader r (StateT s m) where
  ask :: StateT s m r
  ask = lift ask

however, when I want to rewrite it like this, it’s arguing “Couldn’t match type m with ReaderT r m0

instance MonadReader r m => MonadReader r (StateT s m) where
  ask :: StateT s m r
  ask = StateT $ \ s -> fmap (, s) $ ReaderT $ \ r -> return r

but if I write like this, it type checks

instance MonadReader r (StateT s (ReaderT r Identity)) where
  ask :: StateT s (ReaderT r Identity) r
  ask = StateT $ \ s -> fmap (, s) $ ReaderT $ \ r -> return r

I know, that I don’t know a lot, yet and maybe my description is not clear but the main question is: how to rewrite lift ask with direct implementation StateT $ \ s -> fmap (, s) $ ReaderT $ \ r -> return r
Is there a way of how to write direct implementation? just for studying purposes

Thank you in advance

The reason why you cannot write the “direct” implementation without specifying the type like you did in your last example, is that you cannot know what the “next” monad transformer is. The entire point of abstracting ask away from ReaderT to MonadReader is that you do not know how the implementation of ask looks at the time of writing, you just require that there is one (by using the MonadReader r m constraint in your instance declaration). Since this is the only thing that you require, the only thing you can use is ask from that type class.

The reason why this is sensible is because you can easily also have your StateT wrap an ExceptT and only then a ReaderT. The instance for MonadReader r m => MonadReader r (ExceptT e m) then explains how to lift the reader through the ExceptT transformer and again dispatches to some other implementation of MonadReader r m which you do not know but you require to be there. Or you could have an alternative implementation for a ReaderT like monad transformer.

2 Likes

got it, I appreciate you
/thread

It exists a package Monad layers. It is not very popular, but it contains very interesting Overview of different approach of monad transformers.
Maybe this could help you understand mtl a bit more.

2 Likes