What is Vavr?
It is an open source library for Java 8+ which provides data type persistence and functional control structures apparently missed at Java. This tutorial will show you one of the data types available (Value->Either) at Vavr. You should go to the Vavr documentation and see the other functionalities and examples.
What is functional programming?
Functional programming is a programming paradigm that treats computation as the evaluation of mathematical functions and avoids stats or mutable data. This paradigm was introduced at Java 8 with lambda expressions since 2014.
This kind of programming has a number of concepts you should explore and read more, such as First-class and higher-order functions, Pure functions, Recursion, Strict versus non-strict evaluation, Type systems, Referential Transparency, Data Structures.
How can use Vavr in my project?
You can use Vavr in your project by maven, gradle or standalone. You need to target Java 1.8 minimum. More info.
Either
Either is a control structure that represents two possible types. By default, the Left side represents the error and the Right side corresponds to the success side but you can change this behaviour.
This structure has some functional operations such as map, flatMap, filter, fold, etc … I will show you a simple use case with Either (take into consideration this is only to show the functionality of Either), but you should also be able to use it in other complex contexts.
Use case: Divide two numbers. Take into consideration that the user can insert the denominator zero and you should return the following message: “Can’t divide a number per zero!”. If for other reason the division fails you should return “Problem when trying to divide the numbers! Reason: X”, X is the reason why it failed.
Implementing the use case using pure java:
public static Map<String, Object> divideNumber(int num1, int num2) {
Map<String, Object> result = new HashMap<>();
try {
result.put("SUCCESS", num1 / num2);
} catch (ArithmeticException ex) {
result.put("FAIL", "Can't divide a number per zero!");
} catch (Exception ex) {
result.put("FAIL", "Problem when trying to divide the numbers! Reason: " + ex.getMessage());
}
return result;
}
The solution using the Either control structure:
public static Either<String, Integer> divideNumber(int num1, int num2) {
try {
return Either.right(num1 / num2);
} catch (ArithmeticException ex) {
return Either.left("Can't divide a number per zero!");
} catch (Exception ex) {
return Either.left("Problem when try to divide the numbers! Reason: " + ex.getMessage());
}
}
The code using Either seems more intuitive and you don’t need to add the success/fail logic because you can use the right/left side. I created a few unit tests to show operations you can call and compare to pure java (using map structure).
@Test
void testShouldDivideNumberPerZero() {
Either<String, Integer> eitherResult = EitherDivisionUseCase.divideNumber(10, 0);
//You can use isRight or isLeft to check if the value is available
assertFalse(eitherResult.isRight());
assertTrue(eitherResult.isLeft());
//The left side contains the error message (using default behaviour)
assertEquals(eitherResult.getLeft(), "Can't divide a number per zero!");
//If you call the get() method when there is no value you will receive a NoSuchElementException exception
assertThrows(NoSuchElementException.class, eitherResult::get);
//Comparing to pure java using map
Map<String, Object> javaResult = JavaDivisionUseCase.divideNumber(10, 0);
assertNull(javaResult.get("SUCCESS"));
assertNotNull(javaResult.get("FAIL"));
assertEquals(javaResult.get("FAIL"), "Can't divide a number per zero!");
}
As you can see, you only need to use the isRight or isLeft to check if there are values inside the Either structure instead of trying to get a valid key from the mapping.
@Test
void testShouldDivideNumberExceptPerZeroUsingFunctionalOperations() {
Either<String, Integer> eitherResult = EitherDivisionUseCase.divideNumber(10, 2);
//You can call functional operations such as map, flatmap, filter, etc
assertFalse(eitherResult
.map(integer -> integer)
.collect(Collectors.toList())
.isEmpty());
//Comparing to pure java using map
Map<String, Object> javaResult = JavaDivisionUseCase.divideNumber(10, 2);
assertFalse(javaResult.entrySet()
.stream()
.noneMatch(entry -> "SUCCESS".equals(entry.getKey())));
}
Using the pure java, if you want to operate functions like map, filter, etc you need to use Streams from Java. In the case of Either you don’t need it, the library only needs to receive a Function. It will evaluate the right side and if it exists it will call the apply() method from the Function. You can see the code for Either#map below:
/**
* Maps the value of this Either if it is a Right, performs no operation if this is a Left.
*
* <pre><code>
* import static io.vavr.API.*;
*
* class Example {{
*
* // = Right("A")
* Right("a").map(String::toUpperCase);
*
* // = Left(1)
* Left(1).map(String::toUpperCase);
*
* }}
* </code></pre>
*
* @param mapper A mapper
* @param <U> Component type of the mapped right value
* @return a mapped {@code Monad}
* @throws NullPointerException if {@code mapper} is null
*/
@SuppressWarnings("unchecked")
@Override
default <U> Either<L, U> map(Function<? super R, ? extends U> mapper) {
Objects.requireNonNull(mapper, "mapper is null");
if (isRight()) {
return Either.right(mapper.apply(get()));
} else {
return (Either<L, U>) this;
}
}
I hope with this quick use case you were able to catch up with Either. You can consult the Vavr API Either Interface for more information.
Please feel free to comment and give your opinion! :)
Git Vavr-Tutorial use case Division two numbers: https://github.com/nbentoneves/simple-java-tutorial/tree/master/vavr-tutorial
Original Post: http://mydevlife.azurewebsites.net/2019/04/19/vavr-either-tutorial/
References:
Top comments (0)