The Either Class in Vavr
In this article, take a look at the Either class in Vavr.
Join the DZone community and get the full member experience.
Join For FreeVavr library is an excellent tool to make your Java code really functional. One of the 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:
x
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:
x
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:
x
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 filters only students with a good academic standing:
x
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:
- Map each student entity as his/her GPA score
- Use a built-in function
average()
from a VavrList
Take a look on the code snippet below:
x
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.
x
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 aConsumer
function on the right value, but does not modify it, likemap()
(there is alsopeekLeft()
method for a left case)sequence()
= if you have a sequence ofEither
, this method reduces them into a singleEither
instance. The result isEither
that holds sequences for left and right values of all membersswap()
= you can swap a left and a right value, soEither<A,B>
will becomeEither<B,A>
Source Code
You can find a source code for this post as a part of this GitHub repository.
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.
Published at DZone with permission of Yuri Mednikov. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments