Skip to content

Instantly share code, notes, and snippets.

@chrislewis
Created December 29, 2012 22:45
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chrislewis/4409778 to your computer and use it in GitHub Desktop.
Save chrislewis/4409778 to your computer and use it in GitHub Desktop.
A Reader Either monad transformer for java.util.Properties with Scalaz7
/*
* 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