Skip to content

Instantly share code, notes, and snippets.

@digicyc
Forked from tjweir/gist:3437067
Created August 23, 2012 18:56
Show Gist options
  • Save digicyc/3440294 to your computer and use it in GitHub Desktop.
Save digicyc/3440294 to your computer and use it in GitHub Desktop.
reader monad for java.util.Properties example
/* Start by creating a reader for some fake yet conceivable type for executing SQL queries. */
val dbReader: Reader[Properties, JdbcExecutor] =
for {
driver <- read[String]("db.driver")
uri <- read[String]("db.uri")
user <- read[String]("db.username")
password <- read[String]("db.password")
name <- read[String]("db.pool.name")
minCons <- read[Int]("db.pool.minConnections")
maxCons <- read[Int]("db.pool.maxConnections")
idleMins <- read[Int]("db.pool.idlePeriodMinutes")
} yield pooledJdbcExecutor(driver, uri, user, password, name, minCons, maxCons, idleMins)
/* Sweet! But what if we have more than just a JDBC configuration to deal with? Say, a mongo instance... */
val mongoReader: Reader[Properties, MongoService] =
for {
host <- read[String]("mongo.host")
database <- read[String]("mongo.database")
one <- read[String]("mongo.collection.one")
two <- read[String]("mongo.collection.two")
} yield mongoCache(host, database, one, two)
/* Now what? */
val fullReader: Reader[Properties, (JdbcExecutor, MongoService)] =
for {
jdbc <- dbReader
mongo <- mongoReader
} yield (jdbc, mongo)
/*
* Very cool. Now all we need to do is provide the reader an environment, which we've designated as a
* java.util.Properties instance, and we'll get our baked instances.
*/
val props: java.util.Properties = { /* ... load properties ... */ }
val (jdbc, mongo) = fullReader(props)
/*
* Reader monad?
* http://dl.dropbox.com/u/7810909/docs/reader-monad/reader-monad/chunk-html/index.html (thanks Tony Morris)
*
* A reader reads a value A out of some environment C.
*/
trait Reader[C, A] {
def apply(c: C): A
def map[B](f: A => B): Reader[C, B] =
new Reader[C, B] {
def apply(c: C) =
f(Reader.this.apply(c))
}
def flatMap[B](f: A => Reader[C, B]): Reader[C, B] =
new Reader[C, B] {
def apply(c: C) =
f(Reader.this.apply(c))(c)
}
}
/*
* We need a bit more plumbing for building up property readers. First, assume we'll always read values in string
* format and so we'll need transformations:
*/
/** A transformation is merely a String => A, for some type A. */
trait Transformation[A] extends (String => A)
/** Canned transformations intended to ease reuse in some implicit scope. */
trait Transformations {
private def wrap[A](f: String => A) = new Transformation[A] { override def apply(s: String) = f(s) }
implicit val int = wrap { _.toInt }
implicit val string = wrap { identity _ }
// ...
}
/*
* Now we just need a module to get our implicit transformations in scope and to house our read functions.
* This makes it easy to build more complex readers from simpler values (like we might read from properties)
* in a pure, straight forward manner. Plus, because it's a monad, we can glue more and more together such that
* the application of an environment (a Properties instance) will load the environment only once and
* automatically execute the full configuration.
*/
object PropsReader extends Transformations {
def apply[E, A](f: E => A) = new Reader[E, A] {
def apply(e: E) = f(e)
}
def read[A: Transformation: ClassManifest](name: String) =
read[A](name, sys.error("Can't find %s" format (name)))
def read[A: Transformation: ClassManifest](name: String, value: => A) =
PropertiesReader[java.util.Properties, A] { p =>
Option(p.getProperty(name)).map { v =>
try {
implicitly[Transformation[A]].apply(v)
}
catch {
case e: Exception =>
throw new RuntimeException(
"Failed to extract value of type [ %s ] from property [ %s ]!"
format (implicitly[ClassManifest[A]].erasure, name), e)
}
}.getOrElse(value)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment