Scalaz ValidationFrom Workingmouse WikiScalaz Validation is a package ( Consider the scenario where one might process a web form with one or many form fields that require validation. It is true that the processing of these fields either fails for one (or more), in which case, the programmer would like to access the accumulated (one or more) error values of the failure(s), or the application of a function applied across all form fields to produce some universal type with which to continue computation successfully i.e. all form fields validated and we have a result of the application of a function to their values. Anyone familiar with higher-level programming will recognise this as the Applicative Functor pattern across one side of the Either ADT i.e. sequencing a monadic computation. If this terminology is foreign, please ignore it and move on (the remaining explanation is simple). Suppose we have a type case class Person(height: Double, age: Int, name: String) We are going to submit a web form to process a person with three form fields for each component of the Person. We have the following validation rules:
In traditional languages and frameworks, performing this validation is either cumbersome and/or we must escape from our type system, both costs that are very high. Scalaz Validation makes use of pattern matching and algebraic data types and higher-ranked types to achieve validation in a type-safe and concise form. In this example, we will use the traditional Some functions are presented below that are general enough to exist in a supporting library i.e. typically, these functions would be available to the programmer. This is also the case for say, functions that automatically fill out form fields with error messages in a general manner (remember, Scala has XML literals for presenting HTML elements ;). However, these types of functions would violate a mandate of Scalaz to have no additional dependencies, since they would require the Java Servlet Specification (or similar) to exist. Therefore, these functions are deferred to a future library publication (keep an eye out!). The functions below are presented for the purposes of continuing the demonstration of the Scalaz Validation package. They are used for validating form fields into other types such as
import scalaz.Either.threw
import java.lang.Double.parseDouble
import java.lang.Integer.parseInt
def doublep(p: String)(implicit def req: HttpServletRequest): Either[Throwable, Double] =
threw(parseDouble(p))
def intp(p: String)(implicit def req: HttpServletRequest): Either[Throwable, Int] =
threw(parseInt(p))
def minLength(n: Int, s: String)(implicit def req: HttpServletRequest): Either[Throwable, String] =
if(s.length < n) Left(new Error("must be " + n +" or more in length")) else Right(name)
For our first validation rule, we parse the height form field (which is a Here is a long-winded way of writing all this out with commentary along the way.
import scalaz.validation.Validate.combine
def handle(implicit req: HttpServletRequest) = {
// The function for combining all validation rules if validation succeeds
implicit def createPerson(height: Double, age: Int, name: String) = Person(height, age, name)
// We ignore the Throwable from intp here for simplicity of the demonstration
// However, there are functions on Either to assist us if we chose otherwise
def ageInRange(implicit req: HttpServletRequest): Either[Throwable, Int] =
intp("age").mapIf(a => if(a >= 0 && a <= 150))(new Error("age not in range"))
// Let's combine and get a result!
val r = (doublep("height") ~> // validation rule #1
ageInRange ~> // validation rule #2
minLength(6, "name")) |> (capitalise(_))) // validation rule #3 (|> is an alias for map)
// At this point, we have either one or more errors in Left, or a Person in Right as r.
// Let's pattern match to find out.
r match {
case Fail(es) => handleErrors(es) // Validation failed. Write a message on each field in the form?
case Success(p) => processPerson(p) // Validation succeeded, we have a Person (p)
}
}
This code is a little ideal in that the Scala type inferencer may not be able to help you to the level portrayed in this code sample. You may need to add type annotations. This is because the Here is the same code to analyse in terms of its type-safety and conciseness to achieve what we want precisely, without ambiguity and without redundancy, making the code easy to read and reason about (and write tests for! See ScalaCheck). Compared to the previous code sample, comments have been removed and some expressions have been inlined. The value of writing web form field validation using this technique is more prominently acknowledged when one considers that all other code is typically available in general library functions.
import scalaz.validation.Validate.combine
implicit def createPerson(height: Double, age: Int, name: String) = Person(height, age, name)
def handle(implicit req: HttpServletRequest) =
doublep("height") ~>
intp("age").mapIf(a => if(a >= 0 && a <= 150))(new Error("age not in range")) ~>
minLength(6, "name") |> (capitalise(_))) match {
case Fail(es) => handleErrors(es)
case Success(p) => processPerson(p)
}
Can't get any easier than that surely ;)
|
