I have been watching Refactoring some Haskell code to use MTL which refactors some Haskell code to make use of the typeclasses from the mtl package.
The code includes a postReservation function with the following signature:
postReservation :: ReservationRendition -> IO (HttpResult ())
The implementation of the postReservation function makes use of three additional functions with the following signatures:
readReservationsFromDB :: ConnectionString -> ZonedTime -> IO [Reservation]
getReservedSeatsFromDB :: ConnectionString -> ZonedTime -> IO Int
saveReservation :: ConnectionString -> Reservation -> IO ()
In the video, the signatures of the three functions are refactored so that they return a generic type with a MonadIO constraint i.e.
readReservationsFromDB :: (MonadIO m) => ConnectionString -> ZonedTime -> m [Reservation]
getReservedSeatsFromDB :: (MonadIO m) => ConnectionString -> ZonedTime -> m Int
saveReservation :: (MonadIO m) => ConnectionString -> Reservation -> m ()
I understand that doing this makes the functions more flexible as they no longer depend on a concrete monad type or a specific monad transformer stack configuration. I also understand that the postReservation function can still make use of these functions without any changes to its type signature because it has a return type of IO which is an instance of the MonadIO typeclass.
Next, the three functions are refactored to include a MonadReader constraint so that the connection string does not need to be explicitly passed around i.e.
readReservationsFromDB :: (MonadReader ConnectionString m, MonadIO m) => ZonedTime -> m [Reservation]
getReservedSeatsFromDB :: (MonadReader ConnectionString m, MonadIO m) => ZonedTime -> m Int
saveReservation :: (MonadReader ConnectionString m, MonadIO m) => Reservation -> m ()
The signature of the postReservation function is also updated to include the MonadIO and MonadReader constraints i.e
postReservation :: (MonadReader ConnectionString m, MonadIO m) => ReservationRendition -> m (HttpResult ())
The presenter of the video goes on to make a concrete version of the postReservation function called postReservationIO in order to eliminate the typeclass constraints. A broken version of the postReservationIO function is written to demonstrate that it cannot just make use of the postReservation function because the IO type returned by the postReservationIO function is not an instance of the MonadReader typeclass.
We are then told that in order to eliminate the MonadReader constraint from the postReservationIO function we need to make use of the runReaderT function which is where the video loses me.
At about 15:00, the postReservationIO function is refactored to look like this
postReservationIO :: ReservationRendition -> IO (Httpresult ())
postReservationIO req = runReaderT (postReservation req) connStr
The runReaderT function has a type signature of ReaderT k r m a -> r -> m a which I'm reading as a function that takes some concrete ReaderT type and some value of type r (the connection string in our case) and it'll give you back some monad of type m a.
In the postReservationIO implementation we are passing (postReservation req) as the first argument to the runReaderT function. (postReservation req) has the type
(MonadReader ConnectionString m, MonadIO m) => m (HttpResult ())
which as far as I can tell is not a ReaderT so I'm struggling to understand how this works.
Can anyone explain how we have made the jump from something of type (MonadReader ConnectionString m, MonadIO m) => m (HttpResult ()) to ReaderT k r m a in order to eliminate the MonadReader constraint?