FP for the average Joe - I - ScalaZ Validation

FP for the average Joe - I - ScalaZ Validation

What is FP for the Average Joe?

FP for the Average Joe is a series of posts in which we explain functional programming (FP) patterns in a clear way for folks getting started with FP and Scala.

We will feature some of the most useful FP abstractions and utilities introduced in ScalaZ, Shapeless, and other FP- and Type-centric libraries that can help us solve common problems in our programming life.

The case for ScalaZ

We believe FP is important and ScalaZ is a great example of a library that allows for powerful abstractions that we can use to solve common problems with succinct code.

There is a world beyond map, flatMap, filter, and basic exposure to the Scala collections and standard library. After all, many newcomers to Scala see FP as the next frontier and not the end. FP is among the most attractive selling points of Scala, along with Java interoperability.

Now, take a breath. We are going to take it easy and show you real-world examples that you probably face frequently, even if you are new to Scala.

Let’s get started with Validation, a great abstraction to handle errors, exceptions, and success/failure cases.

Validation

Validation can be either a Success or a Failure.

Much like how Try, Option or Either in the standard Scala lib works, Validation is also a container of sorts that lets you obtain information and branch your program logic based on the success or failure of evaluating expression.

Model

We are going to teach by example. First, we’ll show you a simple requirement and evolve its implementation to make it more flexible and robust.

Requirement:

Provide a method to convert from a collection of String to a collection of Int.

Our first implementation looks like this:

def toInts(maybeInts : List[String]) : List[Int] = {
    maybeInts map («_.toInt»)
}
»_.toInt|<code>_.toInt</code> is a lambda that is executed on each one of the elements in <code>maybeInts</code>. If an exception is thrown at any point it will not be captured and it will interrupt execution of <code>toInts</code>«

That method signature is full of assumptions. It’s only by looking at the implementation that we know what is actually going on. If we did not have access to the impl sources we would think that:

  1. It may be discarding strings that can’t be represented as numbers.
  2. It may be bailing with a runtime exception the very first time it encounters an invalid number.
  3. It may simply be ignoring invalid numbers altogether and just returning valid ones.

Neither of them is obvious to the call site.

We can improve error handling here by rewriting its signature and implementation to use Try.

Try is part of the standard library and wraps the evaluation of any expression into Success or Failure. Let’s introduce Try in the return type and impl.

def toInts(maybeInts : List[String]) : Try[List[Int]] = {
    «Try(maybeInts map (_.toInt))»
}
»Try|Try let's you execute a function <code>f: => R</code> and returns either a <code>Success[R]</code> or a <code>Failure[Throwable]</code> with any non fatal exceptions resulting from evaluating <code>f</code>«

That’s a little better. At this point at least it won’t bail with an uncaught exception and we can handled both Success and Failure.

val result : Try[List[Int]] = toInts(List("1", "2", "3"))

result match {
  case Success(numbers) => println(numbers)
  case Failure(ex) => println(ex)
}

Since Try is a monad and includes flatMap, we can even for comprehend over it assuming success and do further transformations in its content.

val «transformedResult» : Try[List[Int]] = for {
  x <- result
} yield x map(_ * 2)
»transformedResult|<code>Success(List(2, 4, 6))</code>«

This still only supports a fail fast strategy and it’s still not possible to do error accumulation. How will we know which strings can’t be turned into numbers?

Enter ValidationNel.

ValidationNel[E, A] is a type alias for a Validation[NonEmptyList[E], A]. It brings the semantics of error accumulation over validation and returns all errors found in a non-empty list.

Let’s break down our function and its implementation to validate all cases and reduce the list of validations into a single result where we can access either the successful value or all errors found when parsing.

def toInts(maybeInts : List[String]): ValidationNel[Throwable, List[Int]] = {
  val validationList = maybeInts map { s =>
    Validation.«fromTryCatchNonFatal»(s.toInt :: Nil).toValidationNel
  }
  validationList reduce («_ +++ _»)
}
»fromTryCatchNonFatal|From the official API docs: <i>Extractor of non-fatal Throwables. Will not match fatal errors like VirtualMachineError (for example, OutOfMemoryError and StackOverflowError, subclasses of VirtualMachineError), ThreadDeath, LinkageError, InterruptedException, ControlThrowable.</i>«
»+++|Sums up values inside validation, if both are success or failure. Returns first failure otherwise.«

Well, this is cool! We can now access both successes and failures. Let see what this looks like in the REPL:

Succeeding

scala> toInts(List("1", "2", "3"))
res1: scalaz.ValidationNel[Throwable,List[Int]] = Success(List(1, 2, 3))

Failing

scala> toInts(List("1", "2", "3", "x", "z"))
res2: scalaz.ValidationNel[Throwable,List[Int]] =
    Failure(
        NonEmptyList(
            java.lang.NumberFormatException: For input string: "x",
            java.lang.NumberFormatException: For input string: "z"))

We can now write our own function to validate transformations in lists. But now this is where it gets more interesting.

Can we further generalize this beyond simple String -> Int conversions?

Let’s rewrite toInts as a generic validation function to accept anything that can be folded over along with a function for transforming the data inside the containers.

def validate[F[_] : Foldable, A, B : Monoid]
    (in : F[A])
    (out : A => B): ValidationNel[Throwable, B] = {
  in foldMap (a => Validation.fromTryCatchNonFatal[B](out(a)).toValidationNel)
}

Now it’s fairly simple to define toInts in terms of validate

def toInts(maybeInts : List[String]): ValidationNel[Throwable, List[Int]] =
  validate(maybeInts)(_.toInt :: Nil)

An extra advantage is that we can now operate over anything that is Foldable not just List in typesafe way

scala> validate(Option("1"))(_.toInt)
res0: scalaz.ValidationNel[Throwable,Int] = Success(1)

scala> validate(Option("x"))(_.toInt)
res1: scalaz.ValidationNel[Throwable,Int] = Failure(NonEmptyList(java.lang.NumberFormatException: For input string: "x"))

scala> validate(Vector("1"))(_.toInt)
res7: scalaz.ValidationNel[Throwable,Int] = Success(1)

Let’s dig into the function definition so we understand what is going on behind the scenes.

Signature:

def validate[«F[_]» «: Foldable», «A», «B : Monoid»]
    («in : F[A]»)
    («out : A => B»): ValidationNel[Throwable, B]
»F[_]|<code>F</code> here is a type constructor that represents anything that may wrap <code>A</code>«
»:Foldable|<code>:Foldable</code> on <code>F[_]</code> constrains all <code>F</code> to have an implicit instance of Foldable for the type that we want to invoke <code>foldMap</code> over.«
»A|<code>A</code> is the actual initial data inside <code>F</code> that can be folded over«
»B|<code>B: Monoid</code> brings in an implicit <code>Monoid</code> instance for <code>B</code>. In order to fold over <code>B</code> given we wrap the result in <code>ValidationNel</code> we need to provide an instance that includes <code>append</code> and knows how to sum <code>ValidationNel[B, E] +++ ValidationNel[B, E]</code>.<br><code>Validation[B, E]</code> can act as a monoid as long as his <code>Success</code> and <code>Failure</code> container types include <code>Monoid</code> instances as well.«
»in : F[A]|The container that can be folded over«
»out : A => B|A function that we can use to transform from <code>A</code> to <code>B</code> and can potentially throw an exception when doing so«

Body:

in «foldMap» (a => Validation.fromTryCatchNonFatal[B](out(a)).toValidationNel)    
»foldMap|applies <code>out</code> on each element folding the results on themselves and sums on each iteration«

We wrap all evaluations of out(a) on Validation.fromTryCatchNonFatal[B] to ensure that all exceptions resulting from the transformation are properly captured.

Finally we invoke toValidationNel to lift the validation resulting from the transformation so that exceptions can be accumulated in the NonEmptyList in case of failure

Our validation function now supports error accumulation and it’s generic enough so that it can be applied over any containers with arbitrary transformations.

ScalaZ also provides multiple utils to lift any value to Validation and ValidationNel

scala> 1.successNel
res3: scalaz.ValidationNel[Nothing,Int] = Success(1)

scala> 1.failureNel
res4: scalaz.ValidationNel[Int,Nothing] = Failure(NonEmptyList(1))

scala> 1.success
res5: scalaz.Validation[Nothing,Int] = Success(1)

scala> 1.failure
res6: scalaz.Validation[Int,Nothing] = Failure(1)

Conclusion

Validation is isomorphic to Either or \/, but unlike those it has the advantage of allowing error accumulation instead of the default fail fast strategy of other types commonly used for error handling.

blog comments powered by Disqus

Ensure the success of your project

47 Degrees can work with you to help manage the risks of technology evolution, develop a team of top-tier engaged developers, improve productivity, lower maintenance cost, increase hardware utilization, and improve product quality; all while using the best technologies.