Created
August 7, 2015 17:03
-
-
Save josdirksen/5c9b9cd92bcd1265ea6d to your computer and use it in GitHub Desktop.
Scalaz, Readers and Validations
This file contains hidden or 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
name := "scalaz-readers" | |
version := "1.0" | |
scalaVersion := "2.11.7" | |
libraryDependencies += "org.scalaz" %% "scalaz-core" % "7.1.3" |
This file contains hidden or 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
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