Introduction
In this post we’ll look at Scalaz Validation which you can use to validate the data in your system. Data validation is a part and parcel of software development; you have to check the data that comes into your system or it may lead to unexpected behavior and / or cause your system to fail. Scalaz provides you with Validation to validate your data. Validation is an applicative functor. An applicative functor has more structure than a functor but less than a monad.[1]
Motivating Example
So let’s say we have a Version class representing the major and minor version of our software like so:[2]
1 | case class Version(major: Int, minor: Int) |
Then, a negative value in either of major or minor would be invalid. We could ensure that we never get a negative value in either the major or minor by using require like so:[3]
1 | case class Version(major: Int, minor: Int) { |
The problem here is that we’ll have to handle exceptions and we don’t want side-effects. Let’s use Validation to do it in a more functional way.
1 | @ import scalaz._ |
What we’ve done here is add a createNew to the companion object of Version that returns a Validation. Validating the input can result in either a Success or Failure. We create a Success by calling success on the value and a Failure by calling failure on the value. Once we have a Validation, there are numerous ways in which we can deal with it.
Providing Default Values
1 | @ val invalid = Version.createNew(-1, -1) |
There’s a convenient | operator (getOrElse) that lets you provide a default value if the result of the validation is a Failure. Here we are assigning the value 1 to major and 0 to minor.
Folding a Validation
1 | @ val valid = Version.createNew(1, 0) |
Akin to a disjunction, it’s possible to fold a Validation. Failure will be the first argument and Success will be the second. If you want to fold just on the Success part, you can use foldRight.
NOTE:
Both fold and foldRight should return values. Here I’ve just printed out the values to the console which is a side-effect. I’ve done this to keep the examples simple.
Composing Validations
1 | @ { |
When we started building the Version example, we used pattern matching to check if the major and minor versions were correct. However, there’s a more succinct way to collect all the errors. In the example above, we’ve turned isValidMajor and isValidMinor into methods that return a Validation instead of simply a Boolean.
The magic is in createNew. Here we convert the Validation into a NonEmptyList. toValidationNel wraps the Failure in a NonEmptyList but keeps the Success as-is. The |@| is an ApplicativeBuilder which lets us combine the failures together. If we get all successes, we construct a Version out of it. Let’s see this in action:
1 | @ val bothInvalid = Version.createNew(-1, -1) |
So, in case of both the major and minor being invalid, we get a non-empty list with both the errors in it. This is very convenient and also very extensible. If you need to add an extra check, you can write a new isValidXXX method and make it return a Validation. You can then use the ApplicativeBuilder to your advantage. Using simply booleans would need checking a large number of possible cases.
Mapping Success and Failure
1 | @ val errMsgs = bothInvalid bimap( |
It’s possible to map over Success and Failure and apply any transformations you want. For example, we represent errors with IllegalArgumentExceptions and we may be coding a rest API and we’d like to send back the strings representing the errors. In the example above, I’ve used bimap to map Success and Failure. For every failure, I am extracting the string using getMessage and leaving the success as-is by using identity.
1 | @ val theVersion = bothValid bimap( |
Similar to bimap is map which just maps the Success part of the Validation.
1 | @ val theValue = bothValid map { v => Version(v.major + 1, v.minor * 2) } |
Remember that bimap and map return a Validation. They don’t extract values out of it. You’ll have to use fold, etc. to get the values out.
Running a Side-Effect on Success
1 | @ bothValid foreach { v => s"Begin build process for version $v".println } |
foreach lets you run a side-effecting function if the result of the Validation is a Success. For example, using the Version, you could begin a build process or similar.
Checking if it is a Success or Failure
1 | @ val isSuccess = bothValid isSuccess |
There are numerous ways in which we can check if the Validation resulted in a Success or a Failure. The simplest way is to use one of isSuccess or isFailure. Similarly, exists will return true if the validation is a success value satisfying the given predicate and forall will return true if the validation is a failure value or the success value satisfies the given predicate.
Converting to Other Datatypes
1 | @ val list = bothValid toList |
Scalaz also provides convenience methods to convert the Validation to different datatypes like List, Stream, Either, etc.
Conclusion
There’s a lot more methods than can be covered in a post. Hopefully this post gave you an idea about what’s possible with Validations.