Either in Vavr

Vavr library is an excellent tool to make your Java code really functional. One of issues, that was not solved by Java itself is an error handling – the language relies on exceptions, although it is not purely functional pattern. As a counterpart, Vavr brings such types as Try and Either, that implement functional concepts. In this post, I would like to share with you the Either class, that offers a predictable return type for methods, that possibly can result into errors. This type makes your codebase more lightweight, that if you will invent your own return types. From the other side, it makes your code more unified and maintainable. Finally, the Either class brings a number of useful methods.

An idea behind the Either type

The traditional Java approach (and C++ too) for error handling is based on exceptions. However, if we will investigate how it is done in functional programming languages, we will find out, that exceptions are not a natural model for them. Moreover, an another Java problem is that it limits you with a single return type – and that issue forces us to “invent” return types like OperationResut<T> (an example name), that can contain information about success scenario (returned object) and information about failure scenario (usually throwable). Once we obtain a value, we test it – likewise it is implemented by Vertx:

service.someOperation(result -> {
    if (result.succeded()){
        // do something
    } else {
        // failure
    }
});

As you can note, that is a mixture of functional and imperative styles. Yes, this pattern avoids a try-catch error handling and allows us to supply a richer information about an error, but it is not yet 100% functional. And this can be solved using an either type.

From a technical point of view, either is a container, that can hold two values – one for successful result and one for failed result. Imagine, that you write a code, that deals with file reading – you use a file service abstraction, that your colleague wrote for you. You don’t need to know how it works, you just call a method, that returns a content of the file. Yet, you know, that it may throw an exception, if file does not exist. Take a look on the following code snippet:

FileReadService service = new FileReadService();
try {
    String content = service.readFile("no-such-file.txt");
    return content;
} catch (BadFilenameException ex){
    System.out.println("No file found");
    return readFromCache();
}

Imagine, that in future, the technical design will require the FileReadService to determine also other error cases. The number of catch clauses with increase… With Either type you can refactor this code as following:

FileReadService service = new FileReadService();
String content = service.readFileSafely("no-such-file.txt").getOrElse(readFromCache());

This style helps you not only to eliminate try-catch blocks, but also to specify a single entry point for all errors to make them to recover from a cache. An another case of using the Either type can be a validation. As it was mentioned, with the Either you can provide a detailed feedback of validation errors:

Either<List<ValidatonError>, Invoice> = validator.validate(invoice);

Advantages of the Either class before your own return types are straightforward:

  • You don’t need to create different return types for each occasion, so you code will become more lightweight
  • Either makes your codebase more unified and mantainable
  • Either class includes a number of useful methods, which incorporate it into functional pipelines

Let now move to practice.

How to map results

We have already defined, that in a nutshell, Either is a container. It has two fields – left value and right value, however the idea is that Either holds only one value, and never both (that is why it called either). Conventionally, values are defined as:

  • Left value = a failure case result
  • Right value = a success case result

In Vavr, Either has numerous useful methods, that fit it into a functional pipeline; and mapping is one of them. You can map a right value and do something with it. It is done using the map() method. Imagine, you work with on a function, that takes a list of students in a class (we don’t care how it is implemented, as we have an abstraction), and then you can build a pipeline, that returns a list of students with a good academic standing:

StudentService service = new StudentService();

List<Student> students = service.findStudentsWithEither("HISTORY201")
        .map(res -> res.filter(student -> student.getGpa() > 4.0))
        .getOrElse(List.empty());
assertThat(students).isNotEmpty().hasSize(5);

By default, Either considers a right value as successful outcome, therefore, the map() method accesses the right value. You can explicitly map the left value with a mapLeft() method. We can chain several map methods, so in order to get an average GPA score in a class we:

  1. Map each student entity as his/her GPA score
  2. Use a built-in function average() from a Vavr List

Take a look on the code snippet below:

BigDecimal average = service.findStudentsWithEither("ART101")
BigDecimal average = service.findStudentsWithEither("ART101")
        .map(res -> res.map(student -> student.getGpa()))
        .map(res -> res.average())
        .get()
        .map(value -> new BigDecimal(value, new MathContext(2)))
        .getOrElse(BigDecimal.ZERO);

assertThat(average).isEqualByComparingTo("3.9");

Please note, that here I sticked with BigDecimal, as I don’t like doubles, and actually it is a good design practice to avoid them in precise computations. So, as the result of the average() method is actually an Option, we additionally convert it to a BigDecimal. I use getOrElse() method, which allows to specify an alternative value, in case the list is empty.

Validate results with filter()

An another thing that makes this type special, is that you could not only to map a right result, but you can also do an assertion of it. There is a method filter(), that takes a logical condition (predicate) to validate a right value.

StudentService service = new StudentService();
BigDecimal average = service.findStudentsWithEither("MATH201")
        .filter(students -> students.nonEmpty())
        .get()
        .map(res -> res.map(student -> student.getGpa()))
        .map(res -> res.average())
        .get()
        .map(value -> new BigDecimal(value, new MathContext(2)))
        .get();
assertThat(average).isEqualByComparingTo("3.9");

In this code we did actually same thing, yet we have moved a list checking to Either. The result of this operation return the Option instance, so we need to call the get() first, in order to access the either. Because we already did a result checking with the filter() method, we don’t need to use getOrElse() to provide an alternative result.

Other notable methods

The Either ‘s functionality is not limited to what we have reviewed so far. There are other methods, that make our developer life easier. In this section we will briefly sum them up:

  • peek() = this method executes a Consumer function on the right value, but does not modify it, like map() (there is also peekLeft() method for a left case)
  • sequence() = if you have a sequence of Either, this method reduces them into a single Either instance. The result is Either that holds sequences for left and right values of all members
  • swap() = you can swap a left and a right value, so Either<A,B> will become Either<B,A>

Summary

In this post we reviewed the Either class from the Vavr library. Basically, it is a container, that can hold a success or a failure result, but not both. This is a concept, originated from functional languages and solves an error handling without try-catch blocks (which is an imperative pattern). We observed how this type works and how to use it to filter or map results. Finally, we listed other useful methods, that are offered by this class.

admin Written by: