Skip to content

Instantly share code, notes, and snippets.

@josdirksen
Created August 7, 2015 17:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save josdirksen/5c9b9cd92bcd1265ea6d to your computer and use it in GitHub Desktop.
Save josdirksen/5c9b9cd92bcd1265ea6d to your computer and use it in GitHub Desktop.
Scalaz, Readers and Validations
name := "scalaz-readers"
version := "1.0"
scalaVersion := "2.11.7"
libraryDependencies += "org.scalaz" %% "scalaz-core" % "7.1.3"
import scalaz._
import Scalaz._
/**
* Simple example showing how to use the reader pattern together with
* the Scalaz provided ValidationNel to apply validations in a functional
* easily extensible manner.
*/
object ValidationSample extends App {
import Readers._
// lets pretend this is the http request (or any other type of request)
// that can be mapped to a map of values.
type Request = Map[String, String]
val Request = Map[String, String] _
// we also define what our reader looks like. In this case
// we define that we want the result to be of type T, and our
// reader expects a Request to process. The result of this reader
// won't be a T, but will be a ValidationNel, which contains
// a list of error messages (as String) or the actual T.
type RequestReader[T] = Reader[Request, ValidationNel[String, T]]
// To experiment, lets say we want to parse the incoming request into a
// Person.
case class Person(firstName: String, lastName: String, age: Int)
// This reader doesn't accumulate the validations yet, just returns them as a tuple of Validations
val toTupleReader = (as[String]("first") |@|
as[String]("last") |@|
as[Int]("age")).tupled // automatically convert to tuple
// this reader converts either to a success tuple, or to a failure NEL
val toAccumulatedTupleReader = (Readers.asString("first") |@| as[String]("last") |@|
Readers.asInt("age"))
.apply((a, b, c) => (a |@| b |@| c ).apply(Tuple3.apply) ) // manually convert to Success(tuple)
// This reader converts to a Success(person) or a failure Nel
val toPersonReader = (as[String]("first") |@|
as[String]("last") |@|
as[Int]("age"))
.apply((a, b, c) => (a |@| b |@| c ).apply(Person) ) // manually convert to case class
// our sample requests. This first one is invalid,
val invalidRequest = Request(Seq(
"first" -> "Amber",
"las" -> "Dirksen",
"age" -> "20 Months"
))
// another sample request. This request is valid
val validRequest = Request(Seq(
"first" -> "Sophie",
"last" -> "Dirksen",
"age" -> "5"
))
// now we can run our readers by supplying a request.
val tuple3Invalid = toTupleReader.run(invalidRequest)
val tuple3Valid = toTupleReader.run(validRequest)
val personInvalid = toPersonReader.run(invalidRequest)
val personValid = toPersonReader.run(validRequest)
val accTupleInvalid = toAccumulatedTupleReader.run(invalidRequest)
val accTupleValid = toAccumulatedTupleReader.run(validRequest)
println(s"tuple3Invalid:\n $tuple3Invalid ")
println(s"tuple3valid:\n $tuple3Valid ")
println(s"personInvalid:\n $personInvalid ")
println(s"personValid:\n $personValid ")
println(s"accTupleInvalid:\n $accTupleInvalid ")
println(s"accTupleValid:\n $accTupleValid ")
// we can further process the tuple using an applicative builder |@|, or
// we can use the Applicative.apply function like this:
// we need to use a type lambda, since we use a higher order function
val V = Applicative[({type λ[α]=ValidationNel[String, α]})#λ]
val appValid: ValidationNel[String, Person] = V.apply3(tuple3Valid._1, tuple3Valid._2, tuple3Valid._3)(Person)
val appInvalid: ValidationNel[String, Person] = V.apply3(tuple3Invalid._1, tuple3Invalid._2, tuple3Invalid._3)(Person)
println(s"applicativeInvalid:\n $appInvalid")
println(s"applicativeValid:\n $appValid")
/**
* Object which contains our readers (or functions that create readers), just simple readers
* that check based on type.
*/
object Readers {
def keyFromMap(request: Request, key: String) = request.get(key).map(Success(_)).getOrElse(Failure(s"Key: $key Not found"))
// convert the provided validation containing a throwable, to a validation
// containing a string.
def toMessage[S](v: Validation[Throwable, S]): Validation[String, S] = {
// Returns new Functor of type self, and set value to result of provided function
((f: Throwable) => s"Exception: ${f.getClass} msg: ${f.getMessage}") <-: v
}
def as[T](key: String)(implicit to: (String) => RequestReader[T]): RequestReader[T] = {
to(key)
}
// Create a requestreader for booleanValues.
implicit def asBool(key: String): RequestReader[Boolean] = {
Reader((request: Request) => keyFromMap(request, key).flatMap({_.parseBoolean |> toMessage[Boolean] }).toValidationNel)
}
// Create a requestreader for intvalues.
implicit def asInt(key: String): RequestReader[Int] = {
Reader((request: Request) => keyFromMap(request, key).flatMap({_.parseInt |> toMessage[Int] }).toValidationNel)
}
// Create a requestreader for string values.
implicit def asString(key: String): RequestReader[String] = {
Reader((request: Request) => keyFromMap(request, key).toValidationNel)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment