The beauty of final-tagless and cats-mtl
In the previous post we implemented a program which precaches users with a help of ReaderWriterState monad. Today we will implement the same program using final-tagless and cats-mtl.
Let’s think about the effects for our program:
- Dependency injection (ApplicativeAsk)
- Logging (FunctorTell)
- State management (MonadState)
- Sequential computation (Monad)
There is an article with ApplicativeAsk’s usage. For producing logs we could use a function tell from FunctorTell. For getting and setting a state — get and set functions from MonadState respectively.
Type aliases for convenience:
Time to implement getUser(uuid: UUID): F[Option[User]] which will make an http call to find a user and populate cache if case of the user was found:
Once getUser function is implemented, preCacheUsers which will iterate over a list of userUuids and call getUser to populate the cache would look like:
The last step is to “commit to a monad”, which will be ReaderWriterStateT:
But wait a minute, can’t we use anything else? And the answer is - Yes!
We could use ReaderT, WriterT, StateT because there are already implementations of ApplicativeAsk, FunctorTell and MonadState in cats-mtl for the monad transformers.
To sum up, cats-mtl is a powerful library which hides all the boiler plate and pain of monad transformers usage. Seems like this is the only seamless way to combine multiple monad transformers. And don’t forget that ReaderT corresponds to ApplicativeAsk, WriterT — to FunctorTell, StateT — to MonadState.