ReaderWriterState monad in action
Reader, Writer, State monads have never been so easy to use in one place with ReaderWriterState monad.
There is IndexedReaderWriterStateT which is implemented in Cats with the type alias ReaderWriterStateT — monad transformer for ReaderWriterState (a bit complicated though). Let’s take a closer look:
To get a good feeling how ReaderWriterStateT works, why don’t we implement a function getUser(uuid: String): IO[Option[User]], which makes an http call, stores result into a cache with a bit of logging.
Cache will be an immutable data structure:
To align with the type parameters for ReaderWriterStateT we should take into account that the context of computation is IO, environment is Environment, log is Log(type alias for List[String]), state is Cache[UUID, User] and result of computation is Option[User]:
ReaderWriterStateT[IO, Environment, Log, Cache[UUID, User], Option[User]]
One of the ways it can be constructed is by using the function:
(Environment, Cache[UUID, User]) => IO[(Log, Cache[UUID, User], Option[User])]
So the body (and I know that it’s ugly) of getUser would look like:
And logic is pretty straightforward: get httpClient from the environment, make an http call. If user was found then return Tuple3: log entry, updated cache and Some(user), otherwise — Tuple3: log entry, cache and None.
The usage is as simple as:
One thing to mention — we have to supply an instance of Environment and an initial state of Cache[UUID, User] in order to run the ReaderWriterStateT.
Once getUser is implemented, our example can be extended with a recursive function preCacheUsers, which will precache users from the list of user UUIDs:
Such function can be used as:
Last but not least it’s worth to take a look at utility methods of ReaderWriterStateT:
ReaderWriterStateT.tell - Add a value to the log, without modifying the input state.
ReaderWriterStateT.ask - Get the provided environment, without modifying the input state.
ReaderWriterStateT.get - Return the input state without modifying it.
ReaderWriterStateT.set - Set the state.
To sum up - ReaderWriterState is an easy way to combine Reader, Writer, State monads together, because no lifting is required, it’s performance overhead is much less and the reason is the same — no lifting of State to Writer to Reader.