Reader Monad Explored- 3 mins
Reader Monad (also referred to as the Environment monad) is commonly used to thread configuration through application code to call sites that depend on it. It offers a form of dependency injection.
You’ve probably noticed that in both examples we’re using the Reader monad for threading state below the top-level entry point to our application. At the top level, we rely upon ordinary functions for passing dependencies.
At this point you might be wondering why we bother to use the
Reader instead of a simple function. That seems to work well enough for the applications top-level, so why not for the remainder of the application?
Reader is not without its problems.
Each function of a module (.e.g, DAO) replicates the Reader monad signature. This is fine for small modules, but becomes tiresome and error prone as the module size increases. Refactoring becomes a pain if the
Reader signature changes as well.
The advantage of being able to write new functions in terms of existing functions that return a
Reader turns out to not come up that often in my experience. I have used it a few times, but I’m not sure that I gained a ton from its use.
The well-known awkwardness of dealing with stacked monads (in the absence of Monad Transformers) receives an additional monad on the stack. It would not be unusual for a
DAO module to have a function with the signature
id => Reader[Env, Future[Either[Error, Maybe[Model]]]]. This is unwieldy.
After using this pattern in several applications, I’ve decided that while it’s a nice tool to have in your belt, other methods for dependency injection are better suited to most use cases.