Created
August 21, 2012 15:22
-
-
Save chrislewis/3416494 to your computer and use it in GitHub Desktop.
reader monad for java.util.Properties example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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