Created
December 29, 2012 22:45
-
-
Save chrislewis/4409778 to your computer and use it in GitHub Desktop.
A Reader Either monad transformer for java.util.Properties with Scalaz7
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
/* | |
* This is a rendition of https://gist.github.com/3416494. Scalaz7 provides the | |
* Reader and we take it a step further by constructing and using a Reader Either | |
* monad transformer with pure error handling, instead of the original impure Reader | |
* monad that would throw exceptions on parse errors. | |
*/ | |
import scalaz._ | |
import Scalaz._ | |
import java.util.Properties | |
/** A Reader with error effects through an Either bound in some error type. */ | |
type ReaderTEither[E, A, B] = ReaderT[({type λ[+A] = E \/ A})#λ, A, B] | |
object ReaderTEither extends KleisliFunctions with KleisliInstances { | |
def apply[E, A, B](f: A => E \/ B): ReaderTEither[E, A, B] = kleisli[({type λ[+A] = E \/ A})#λ, A, B](f) | |
} | |
/** A transformation is merely a String => A, for some type A. */ | |
trait Transformation[A] extends (String => A) | |
/** Transformations for basic types. */ | |
trait Transformations { | |
private def tx[A](f: String => A) = | |
new Transformation[A] { | |
override def apply(s: String) = f(s) | |
} | |
/* Transformations for basic types. */ | |
implicit val int = tx { _.toInt } | |
implicit val str = tx { _.toString } | |
/* Auto-lifted transformations. */ | |
implicit def opt[A](implicit t: Transformation[A]) = tx { Option(_).map(t) } | |
implicit def list[A](implicit t: Transformation[A]) = tx { _.split(",").map(t).toList } | |
/* Lift transformations into error-tolerant forms. */ | |
implicit def liftEither[A](implicit f: Transformation[A]) = | |
tx { s => \/.fromTryCatch(f(s)) } | |
} | |
object PropsReader extends Transformations { | |
/** Construct a Reader Either transformer that will read and parse a named value | |
* from a classic java.util.Properties environment. If parsing fails the error | |
* will be recorded and no further parsing will occur. Note that we're using | |
* transformations lifted into Scalaz's Either, and so when they fail a left | |
* value is returned instead of an exception being thrown. We harness the | |
* monad transformer to combine the effects of reading from an environment and | |
* halting on an error. | |
*/ | |
def readE[A](name: String)(implicit f: Transformation[Throwable \/ A]): ReaderTEither[Throwable, Properties, A] = | |
ReaderTEither { p => f(p.getProperty(name)) } | |
} | |
import PropsReader._ | |
/* Start by creating a reader for some fake yet conceivable type for executing SQL queries. */ | |
val dbReader = | |
for { | |
driver <- readE[String]("db.driver") | |
uri <- readE[String]("db.uri") | |
user <- readE[String]("db.username") | |
password <- readE[String]("db.password") | |
name <- readE[String]("db.pool.name") | |
minCons <- readE[Int]("db.pool.minConnections") | |
maxCons <- readE[Int]("db.pool.maxConnections") | |
idleMins <- readE[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 = | |
for { | |
host <- readE[String]("mongo.host") | |
database <- readE[String]("mongo.database") | |
one <- readE[String]("mongo.collection.one") | |
two <- readE[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.run(props) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment