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?