Composing functions with Reader monad
Today we will learn what is a Reader Monad and how to use it.
Reader Monad can be extracted from any code which uses constructor injection in 6 steps. Let’s take a look at UserRepository
code snippet:
Step 1 — move ConnectionPool
as input parameter to createUser
function:
Step 2 — make createUser
curried:
Step 3 — change return type of createUser
from User
to function ConnectionPool => User
:
Step 4 — create a wrapper for the return type of createUser
let’s call it Reader
:
Step 5 — generalize Reader
with type parameters:
Step 6 — add map
and flatMap
to the Reader
:
And here you are — Reader Monad.
Think of A
as an Enviroment — place where you keep your dependencies, in the previous example it was ConnectionPool
but could be something like:case class Environment(userService: UserService, httpClient: Client)
Luckily cats
library provides Reader Monad out of the box.
The main purpose of the Reader monad is to compose functions and delay dependency injection phase until the later moment:
Combining Reader together with other monads requires to write a bit of boilerplate:
And ReaderT is here to help to reduce the boilerplate:
Reader Monad can also be used for passing a Diagnostic Context
.
Image that you have to add requestId
to every log message in your code base, thus you are forced to pass requestId
in every function. While with the Reader Monad information about Diagnostic Context
will be embedded in return type.
For composing Readers with different environments take a look at the next example, the idea is to align environments using local
:
That’s it for today.