Introduction to vavr(A functional library for Java)

Table of contents
Reading Time: 8 minutes

Introduction
In this post, I will be talking about Vavr and it’s modules. Vavr is essentially a functional Java library which aims to extend the features of Java8 and helps to reduce the amount of code and increase code quality. It provides collections, functional interfaces for error handling, concurrent programming, and pattern matching.There are many other modules which revolves around vavr core.

Vavr provides the necessary controls and collections to accomplish the immutable goal in java programming. Also, It provides immutable data structures(cannot be modified once created), persistent data structures(does preserve the previous version of itself when being modified), functional data structures(also known as purely functional data structures), they are immutable and persistent. If you are familiar with Scala language and has recently switched to Java8(Functional java) and miss the features of Scala, you are going to love this library as this library has pretty much all the features of Scala. Come, let’s explore the library now.

Note – Vavr is not depending upon any library other than JVM, so you can also add it as a JAR in the classpath of your project if you are not using Maven or Gradle.

That was all about the introduction to Vavr. Now, let’s get started.

Maven
To use vavr add the following dependency in your Maven project.



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


<dependencies>
<dependency>
<groupId>io.vavr</groupId>
<artifactId>vavr</artifactId>
<version>0.9.3</version>
</dependency>
</dependencies>

With gradle you can add the following dependency

dependencies {
    compile "io.vavr:vavr:0.9.3"
}

Note – Please do check for latest version.

Additional Note – Vavr is not depending upon any library other than JVM, so you can also add it as a JAR in the classpath of your project if you are not using Maven or Gradle.

Let’s see what all we have got with Vavr.

Tuple – You must have seen Tuples in Scala if you have ever worked on Scala. Tuple is nothing but a data structure which will have fixed number of elements and can hold objects of heterogenous types(different types). So, now you can return two or more values from a function because if you can return a tuple from a function, of course you can return multiple values and the best part is tuple is immutable.
Similar to Scala, now we can also use Tuple in Java. This feature is probably the most sought feature by Java developers which was not introduced as part of Java8. However, with vavr, now you can have tuples and use them.

Create a tuple using static factory method.

Tuple2 tuple2 = Tuple.of(“OK”,”BAD_REQUEST”)

Access first value from a tuple

tuple2._1

Access second value from a tuple

tuple2._2

Apply map to a tuple – You can also apply map to a tuple and transform value as and when required.

Tuple2 transformedTuple = getStatus().map(first -> first.concat(" 403"),

second -> second.concat(" 200")); // transforming values of a tuple

There are several other methods which can play around with.

Example –



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


public class TupleDemo {
public static void main(String[] args) {
Tuple2<Integer, Integer> tuple2 = Tuple.of(1, 2);
System.out.println(tuple2._1); // to retrieve first value from Tuple
System.out.println(tuple2._2); // to retrieve second value from Tuple
System.out.println(getStatus()); // function returning multiple values with the help of Tuple
Tuple2<String, String> transformedTuple = getStatus().map(first -> first.concat(" 403"),
second -> second.concat(" 200")); // transforming values of a tuple
System.out.println(transformedTuple);
}
private static Tuple2<String, String> getStatus() {
return Tuple.of("Rejected", "Passed");
}
}
view raw

tuple_demo.java

hosted with ❤ by GitHub

Funtions in Vavr –
In Java8 we have Function which accepts one parameter and BiFunction which accepts two parameters whereas vavr provides functions with upto a limit of 8 parameters. The functional interfaces are like this Function0, Function1, Function2 and so on. Also, if you want to use a function which throw a checked exception you can use CheckedFunction1, CheckedFunction2 and so son.

Create a function using vavr.

Function2 sum = (first, second) -> first + second;

Use the function you just created

System.out.println(sum.apply(100, 100));

You can also create functions using static factory methods like below

Function3 addition = Function3.of(FunctionDemo::sumOfThreeNumbers);

Call the above function with the help of apply method like below

System.out.println(addition.apply(5, 5, 5));

Function composition – We can also compose functions using vavr just like Java8. Function composition is nothing but output of one function becomes the input to second function and creates a new function(third).

Let understand the same with the help of Maths.

H:g(f(x)) – where out of f(x) becomes the input to g function and finally produces a new function.

For function composition we can either use andThen() or compose function from Funtion( Funtional interface).

Let’s see function composition in action



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


Function1<Integer, Integer> incrementOne = number -> number + 1;
Function1<Integer, Integer> incrementTwo = number -> number + 2;
//with andThen function
Function1<Integer, Integer> resultantFunction = incrementOne.andThen(incrementTwo);
System.out.println(resultantFunction.apply(5));
//with compose function
Function1<Integer, Integer> resultantFunctionWithCompose = incrementOne.compose(incrementTwo);
System.out.println(resultantFunctionWithCompose.apply(5));

Function composition with both functions andThen and compose result in same output.

Lifting in Functions

We can convert a partial function into a normal function which returns an Optional result. A partial function is nothing but a function which might not produce output for all the input values and may throw exception for input values which are not allowed at all.

We would using a function with name lift() to convert partial function into a total function.

Let’s see lifting of function in action.



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


//lifting
Function2<Integer, Integer, Integer> divide = (first, second) -> first / second;
Function2<Integer, Integer, Option<Integer>> lift = Function2.lift(divide);
// None
System.out.println(lift.apply(1, 0));
//Some value
System.out.println(divide.apply(1, 1));

A function which is lifted returns either None(when exception occurs) or Some(Successful case).

Partial application – Partial application will let you derive a new function from an existing function by fixing some values. It’s upto us how many values we want to fix for an existing function. Now new function will have Original number of arguments – fixed number of arguments for new function and the parameters are always boud from left to right.

Let’s see it in action.



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


//partial application
Function4<Integer, Integer, Integer, Integer, Integer> fourArgFunction = (a, b, c, d) -> a + b + c + d;
Function2<Integer,Integer,Integer> result = fourArgFunction.apply(4,5);
System.out.println(result.apply(1,2));

Where 4,5 will fixed for argument a and b respectively and resultant function will need to pass only 2 arguments i.e original arguments(4) – fixed argument(2).

Currying in Function – Currying looks similar to partial application until the resultant function in invoked. Both looks to be similar when the values are fixed. However, the resultant function invocation makes the difference between two.

Let’s see currying in action.



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


//currying
Function1<Integer, Function1<Integer, Function1<Integer, Integer>>> curried = fourArgFunction.curried().apply(2);
System.out.println(curried.apply(2).apply(1).apply(1));

If you look at both partial application and currying there is a difference

1. You can have only one argument in apply function, you have multiple apply though to reduce the function composition.

2. While invoking the result function unlike partial application you will have to invoke multiple apply function depending upon the original nunber of arguments only then you will get the result.

Memoization – Memoization is nothing but caching of the result. SO, what happens is, memoized function is executed only once and then returns the result from a cahce.

Let’s see memoization in action.



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


//memoization
Function2<Integer,Integer,Integer> multiply = (a,b) -> (a *b);
Function2<Integer, Integer, Integer> memoized = multiply.memoized();
Integer value = memoized.apply(2, 3);
System.out.println(value);

Values
An expression which can be further evaluated for calculations or other. In Java, this can be expressed by making the state of an object final and call it immutable. Vavr’s functional value abtracts over immutable objects. If variable and objects are immutable their state is unmodifiable and unchangeable which provides thread safety. So, we can share the variable without any fear in a mult threaded environment.

Let’ see what all do we have in Values
Option – We saw Option in Scala language. However, in Java something similar was introduced with the help Optional class which behaves pretty much similar to Scala’s Option. Instances of Option are either an instance of Some or None. Honestly speaking I don’t see much difference in Scala’s Option and Java’s Optional. If you are a Scala developer who has always worked Option and want to continue with the same, you can of course use this vavr library to make use of Option in Java as well.

Let’s see Option in action



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


Option<String> value = Option.some("Deepak");
Option<String> value1 = Option.none();
System.out.println(value.map(a -> a).getOrElse("Default name")); // Deepak
System.out.println(value1.map(a -> a).getOrElse("Default name")); // Default name
view raw

option.java

hosted with ❤ by GitHub

This Option is pretty much similar to Scala’s Option.

Try – Try is nothing but a container that represents a computation that may either result in an exception or return a value. This looks similar to Either but it is different than that. Instances of Try, are either Success or Failure

Let’s see Try in action.



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


CheckedFunction2<Integer,Integer, Integer> divide = (a, b) -> a/b ;
Try<Integer> failure = Try.of(() -> divide.apply(5, 0));
System.out.println(failure);
Try<Integer> success = Try.of(() -> divide.apply(5, 1));
System.out.println(success);
view raw

try.java

hosted with ❤ by GitHub

Above code will return following output.

Failure(java.lang.ArithmeticException: / by zero) //For failure statement

Success(5) //For success statement

Try will either return Success(if the statment does not result in exception) or Failure(if the statment results in exception)

Lazy – Lazy is a container which will represent a lazy evaluated value. Lazy is memoized already, it evaluates only once and next time onwards returns from a cache.

Let’s see Lazy in action.

You can create lazy values using a static factory method like below



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


Lazy<String> name = Lazy.of(() -> "Deepak");
System.out.println(name.isEvaluated()); //false
System.out.println(name.get()); //Deepak
System.out.println(name.isEvaluated()); //True
System.out.println(name.get()); //Deepak, from cache
view raw

lazy.java

hosted with ❤ by GitHub

Either – An instance of Either is either an instance of Left or Right. A common use of Either is as an alternative to Option for dealing with possible missing values. You can relate it Option as well, so Left is considered to be None type and Right is considered to be Some type. Instance Right will hold success and Left will hold failure.

Let’s see Either in action.

Either integers = functionWithEither().right().map(value -> value + +1).toEither();

where functionWithEither is defined as follows

private static Either functionWithEither(int number) {
        if (number >0) {
            return Either.right(number);
        }
        return Either.left("Please input a valid number");
    }

Note – We do not have Either in Java8. However, vavr library provides it.

Future – A Future represents the result of an asynchronous computation. When the asynchronous task is created, a future object is returned
There is a get() with Futures which will compute the result from Future but it is blocking in nature. It also has isComplete() which returns a boolean value to mention if a future has been completed.

A future has two states: pending and completed

Pending – The computation is on going. A pending future may be completed or cancelled.

Completed – The computation finished successfully with a result, failed with an exception or cancelled.

Java8 already has a rich set of APIs for future. Future has been there since Java5 and it has improved with time. Java8 has provided improvements to it. Now, we have CompletableFuture(class) and CompletionStage(interface) with several callback methods.

I personally love Java8’s future API. Vavr has also introduced future class in the library with some additional feature which we can play around with.

Let’s see Futures in action



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


CheckedFunction0<Integer> checkedFunction1 = () -> 2;
Future<Integer> of = Future.of(checkedFunction1);
System.out.println(of.get());
view raw

future.java

hosted with ❤ by GitHub

Collections – In vavr library a new collection library has been introduced to meet the functional requirements, which is immutable in nature. Vavr library will essentially reduce the biolerplate code which we will see going forward. This new collection is essentially based on java.lang.Iterable.

List in Vavr – Vavr list is essentially an immutable linked list. Mutations will result in new instances.

Comparison between Java8 and Vavr

Java8

//Java8
Optional reduce = Stream.of(1,2,3,4,5).reduce((a,b) -> a + b);
System.out.println(reduce.get());

//Java8
int sum = IntStream.of(1,2,3,4,5).sum();
System.out.println(sum);

Vavr

//vavr
Number sum1 = List.of(1,2,3,4,5).sum();
System.out.println(sum1);

Vavr’s List has got rich set of function of course and it is less verbose compared to Java8.

Stream – We also have Stream in vavr just like Java8 but there is a difference here, Stream in io.vavr.collection package is lazy linked list which means values will be computed only when needed.

Stream in action

Stream.from(2).filter(i -> 1%2 == 0)

We have several other function in Stream, which you can explore and play around with.

Pattern Matching – Pattern matching was not intriduced as part of Java8, although we have wokred with pattern matching in Scala but Java never had it. Vavr library has support for pattern matching, now you can also use pattern matching in Java8.

In pattern matching match is essentially an expresssion which yields some result. Also, pattern matching is really a great feature to save a lot of time as it avoids to write if else branches and the code looks more clean and readable.

Vavr library provides a match API which is pretty much similar to Scala’s pattern matching.

To enable this import the following package

import static io.vavr.API.*;

Now, let’s understand the methods we have in API, we have static methods like Match, Case and other.

Let’s see pattern matchin in action



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


String number = Match(2).of(
Case($(1),"one"),
Case($(2),"two"),
Case($(),"default")
);

Where
$(value) – equals pattern, which means you have value matching case.

$() – wild car pattern, which is will have the default value in the scenario where all the cases fail to match. This also saves from MatchError which is thrown if no case matches

$(predicate) – conditional pattern, you can also have conditional value for cases.

Note- In vavr, they are using uppercase method names because ‘case’ is already a keyword in Java which is used with switches.

Conditional pattern in action

Vavr already provides a set of default predicates here which can be found in package below.

import static io.vavr.Predicates.*;

Can be used in code like below.



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


//conditional pattern
String conditional = Match(1).of(
Case($(is(1)),i -> "one" +i),
Case($(is(2)),i -> "two" + i),
Case($(),"default")
);

Named parameters – Vavr uses lambdas to provide named parameters for matched values

Named parameter in action



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


String conditional = Match(1).of(
Case($(is(1)),i -> "one" +i),
Case($(is(2)),i -> "two" + i),
Case($(),"default")
);

That’s pretty much it, I believe, we covered almost all the features of the library. I have also created a github repository which has all the examples used in this post, link for the repo here. If you loved this post please help me spread this. Also, If you have any query or suggestions with regards to the post, please do let me know in comments. Keep smiling, Keep coding 🙂


Knoldus-blog-footer-image

Written by 

Deepak is a Software Consultant having experince of more than 5 years . He is very enthusiastic towards his work and is a good team player. He has sound knowledge of different technologies which include Java, C++, C, HTML, CSS, Javascript, C# always keen to learn new technologies.

Discover more from Knoldus Blogs

Subscribe now to keep reading and get access to the full archive.

Continue reading