Functional Programming with Validated

This blog post is 4th in a sequence of 5, as a result of our Coding Guild session on August 1st about functional programming concepts. In the session, TypeClasses, Semigroups, Monoids, Functors and Validated were covered. The Cats library has been used, but hasn’t been introduced extensively.

This time we will cover Validated. Validated is categorised in the Cats library as a data type, not a typeclass. But the way it works is very similar to using typeclasses.Validated is introduced in the Cats library as follows:

Imagine you are filling out a web form to signup for an account. You input your username and password and submit. Response comes back saying your username can’t have dashes in it, so you make some changes and resubmit. Can’t have special characters either. Change, resubmit. Passwords need to have at least one capital letter. Change, resubmit. Password needs to have at least one number.

Or perhaps you’re reading from a configuration file. One could imagine the configuration library you’re using returns a scala.util.Try, or maybe a scala.util.Either. Your parsing may look something like:

You run your program and it says key “url” not found, turns out the key was “endpoint”. So you change your code and re-run. Now it says the “port” key was not a well-formed integer.

It would be nice to have all of these errors be reported simultaneously. That the username can’t have dashes can be validated separately from it not having special characters, as well as from the password needing to have certain requirements. A misspelled (or missing) field in a config can be validated separately from another field not being well-formed.

As you see, the result type of the individual validations is Validated[NEL[String], Something]. If all validations comply, it returns a Valid(Something). If not (so if any of validations fail) it returns a Invalid(NonEmptyList[String]) where the Strings in the NonEmptyList are, in our case, error messages.

If the validation can be represented by a Boolean, as in our validatePostalcode, the validating function can be shortened:

---- ParallelValidateTyped ----
Valid(Location(City(Amsterdam),Postcode(1234AB)))
Invalid(NonEmptyList(1234ABC is not a valid PostalCode))
Invalid(NonEmptyList(Rotterdam is not a valid city))
Invalid(NonEmptyList(Rotterdam is not a valid city,
1234ABC is not a valid PostalCode))

We got our parallel validation. The way it is set up, it requires to enumerate all possible combinations for each set of validations. A more generic approach is:

---- ParallelValidate2 ----
Valid(Location(City(Amsterdam),Postcode(1234AB)))
Invalid(NonEmptyList(1234ABC is not a valid PostalCode))
Invalid(NonEmptyList(Rotterdam is not a valid city))
Invalid(NonEmptyList(Rotterdam is not a valid city,
1234ABC is not a valid PostalCode))

We replaced all specific types by type parameters. To be able to enumerate all errors, we define our error type E as a Semigroup, which makes it possible to combine() the errors. Moreover, we supplied a function that maps the outcome of the validators to the requested result type. Note that the arity of the latter function is the number of validations that are executed in parallel. The parallelValidateN explodes if we want to do more validations in parallel: for N = 2 we have 4 cases to check, for N = 3 it is 8, and in general it is 2 ** N …

---- ParallelValidateCartesian2 ----
Valid(Location(City(Amsterdam),Postcode(1234AB)))
Invalid(NonEmptyList(1234ABC is not a valid PostalCode))
Invalid(NonEmptyList(Rotterdam is not a valid city))
Invalid(NonEmptyList(Rotterdam is not a valid city, 1234ABC is not a valid PostalCode))

The Cartesian product operator basically allows to combine stuff, which can be illustrated by the Cartesian product of two lists (from herding cats – Cartesian):

Admitted, the validations are not very realistic, but they do illustrate the way Validated works in combination with Cartesian, and that's what is intended. And if you want to report all errors in a certain validation, Validated combined with the CartesianBuilder syntax are certainly a way to consider.