September 6, 2008, Saturday, 249

Scalaz Validation

From Workingmouse Wiki

Jump to: navigation, search

Scalaz Validation is a package (scalaz.validation) of the Scalaz project. It is used to validate the combination of potentially failing function application while accumulating errors in the event of any one failure such that the entire validation fails, otherwise, if no functions fail validation, the computed result is available. It is applicable to web application form field validation, however, it is not restricted to this type of application. Validation depends heavily on the Either Algebraic Data Type, which is also included with Scalaz (scalaz.Either).

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 Person made up of three components:

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:

  1. The height field is a valid Double value when parsed
  2. The age field is a valid Int value when parsed and is a positive value between 0 and 150
  3. The name field is at least 6 characters long and it is capitalised correctly if it validates

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 Throwable type to represent validation failure, however, any type can be used and this is certainly encouraged. For example, you might keep the name of the form field that failed validation along with a message so that you can present this message back to the user at that web form field.

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 Double and Int in this example using Throwable for representing validation failure. Note that you can construct any other type for failure on top of these functions simply by using the .mapLeft method on Either. Also, you can add further restrictions after parsing by combining another validation rule.

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 String) and if parsing succeeds, we return that value using the doublep function. We use the intp function for validating the parsing of the age form field and sequence the (using Either methods) additional restriction (between 0 and 150). We construct a function out of minLength and the capitalise function (omitted for brevity) for our name form field.

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 combine function from the Validate object uses higher-ranked types, which are not as advanced in Scala's type inferencing as they are in other advanced languages (current at Scala version 2.6.0). To have these type arguments applied using List and NonEmptyList, use the ValidateList object and its combine functions. This will alleviate any consequences of deficiencies in the Scala type inferencer. These functions may also be easier to understand for users who are unfamiliar with higher-level programming concepts such as higher-ranked types.

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 ;)


  • The Scalaz project
  • The ScalaCheck properties for the Scalaz Validation project are specified here.