|
We basically defines two operations accepting an input parameter each, and return the result of the combined functions. The more interesting part being that each function reads from the same input source. I guess that's why we call the pattern the "Reader" Monad.
How many times did we encounter this situation when we had to apply different operations onto an input context from which we aimed to extract, then process values ?
The most recent example that came to my mind was setting up an environment from accessible configuration information. In his book, Joshua Suereth explains how to produce then combine optional Config instances while building an application. Starting from there and using our new pattern just see how we can simulate the whole scenario:
case class Config[T](data: T)
implicit object ApplicativeConfig extends Applicative[Config] {
def apply[T](data: T) = Config(data)
def flatten[T](m: Config[Config[T]]) = m.data
def map[T, P >: T, U](source: Config[T])(f: (P) => U) = Config(f(source.data))
}
case class Connection()
case class Datastore()
case class Application()
case class Environment(datastore: Datastore, application: Application)
def connection(map: Map[String, String]): Config[Connection] = Config(Connection())
def datastore(conn: Config[Connection])(map: Map[String, String]): Config[Datastore] = Config(Datastore())
def application(map: Map[String, String]): Config[Application] = Config(Application())
def build = (a: Application, d: Datastore) => Environment(d, a)
def configBuilder = for {
conn <- connection _
store <- datastore(conn) _
app <- application _
} yield (build:@:app:*:store)
println(configBuilder(Map())) |
|