Our thoughts, knowledge, insights and opinions

Handling failure using Xor and Validated data types

Introduction

Any application sooner or later will fail. Imperative style programming usually handles this using side-effects by propagating exceptions and handling them later on. This approach introduces statefulness and deferring the error to outer bounds of the application. This creates hidden control-flow paths, that are difficult to reason about and debug properly when the code grows too much.

The functional and side-effect free approach that resolves this problem, is wrapping the result of the computation in a datatype, that will represent the possibility of the computation failing. Of course this also means that anything using this kind of value with a failure context needs to take into account the possibility that the result represents a failure and act accordingly.

Scala already has data types, that we can use to represent failure in such a manner - Option when the value might not exist, Try when computation might fail with exception and Either to signal that two distinct types can be used. The most “general” one is Either and thus we will use it to show the concept explained beforehand.

Either introduces two container types: Left and Right, each capable of storing different type of value. It is often used for returning results, that could represent either successful computation or failure message. For example the Left value can be of some type that will represent an erroneous result of the computation. The Right value represents the successful result type, e.g.:

In this simple example we handle the possible failed state of the computation (division by 0) by simply returning a String that describes the error. Of course, a better idea would be using some kind of algebraic structure that would represent all of the possible erroneous states, instead of processing arbitrary strings. We will do exactly that in one of the future examples where we will reimplement the above function using Xor.

If you are into functional programming in Scala you probably already heard about (or used) Scalaz and it’s successor - Cats. Both of them include types specifically designed for handling with errors similarly to the one presented in the above example. Those types could be taken as more specialized extensions of Either designed for failure handling, in fact they are isomorphic (one can be transformed into the other and vice versa). In the following part of the post I would like to show you the data types provided by the Cats library that replace Either for handling failure. I will explain how to use them and what is the main difference between each of them.

Xor

Xor is almost the same as Either (they’re isomorphic, i.e. one can be freely transformed into the other) but there are two important differences that make Xor a better solution in most cases:

  • Xor is a Monad (simply put in a practical context it has a map and flatMap function)
  • It’s right-side biased by default, i.e. the Right value of Xor represents success. This produces some differences on how some functions work when using it (e.g. map will only apply to the right-sided value/type)

Xor is an algebraic datatype represented in the following way (simplified):

Xor having a flatMap enables us to use it in for-comprehensions. When used in this fashion Xor will “short-circuit” the computation whenever it encounters a failure (a Left value).

In the following example, I will try to show how we can use Xor to represent possible failure in a similar fashion as with Either and additionally using it to stop a computation represented by a for-comprehension, when it encounters a failure (represented by Left). So here it is:

The |+| Cats operator represents the combine function, that must exist for any datatype of the Semigroup type class. For the right-sided type Double it simply means adding two numbers and the evidence for Double being a Semigroup is delivered by Cats. If the value is Left then it will return the same Left.

The result of this computation will be Left(DivisionByZero). The third division divide(3.0, 3.0) will be omitted because divide(1.0, 0.0) resulted in a Left. If there wouldn’t be any failures the result would simply add up the results of the divisions.

As we said before Xor has a right-side biased map function. We can use it to transform the successful result stored in the Right value, e.g. using the last function we created:

Validated

The second datatype provided to us by Cats for failure handling is called Validated. It allows us for overall validation, which is impossible when using Xor as only the very first failure is propagated as the result when chaining them using flatMap.

This difference arises from the fact that Validated is not a Monad, which implies there is no flatMap function for it. This means You can’t use it within a for-comprehension because it doesn’t have any concept of “failure” as it is designed to accumulate all of the errors.

We compose Validated contexts using the fact that it is an Applicative, that basically tells us how to handle functions that are inside the Validated context and apply them onto values inside other Validated instances.

The algebraic datatype Validated is represented in the following way:

The type held in the Invalid value should be of the Semigroup type class, so that we can combine errors at each step. This is usually done by using the list type, i.e.:

The Semigroup evidence for List is supplied here by the standards Cats library.

Usually, when the value is Invalid and stored in a List we want to ensure that it is non-empty. This is pretty obvious as we want something stored in it that will describe what error/failure occurred. This can be ensured by using the Cats NonEmptyList type. It ensures that the List that it wraps around will always have at least one element.

NonEmptyList and Validated can be used together and, in fact, that combination is very common. Thus Cats provides us already with an type alias along with helpful functions that we can use to operate on this kind of construct:

Well this basically explains what is Validated and the logic behind it. Here I would like to show a very basic example of using it for creating an object holding validated user data. Checking the password length and email pattern are done independently. The code:

So basically we apply the validation helper function onto the User constructor function. The resulting Validated instance will either hold a valid User instance or a non-empty list of errors that occurred during the validation phase (password too short, e-mail pattern was wrong or both).

Conclusions

So we are at the end of this basic introduction into handling failures with Cats and the benefits that it presents. This by no means exhausts all the information on the topic. There is a plethora of helper functions provided for both types, e.g. for transforming from the standard Either, Option and Try types into Xor or Validated, transforming Xor into Validated and the other way around. I have provided useful links at the end for further reading about the subject.

Thanks for reading and I hope for Your success in using the knowledge learned here!

You like this post? Want to stay updated? Follow us on Twitter or subscribe to our Feed.


comments powered by Disqus