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 IllegalArgumentException
s 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 Validation
s.