Using Functional Programing to get better code in Java

RicDev2
7 min readMay 31, 2021

As software developer in a company always exist projects where the code for applications is not readable and useful even get configured it is a challenge. For those who are dedicated to this profession is everyday to deal with this. I’m Java Programmer and I want to write this post for share some of the solutions that I found to solve the problem with awful code. Projects where the time and resource are limit tends to produce code created with urgently and provocate bugs. If see this problem only from bad perpective you probability will not resolve the problem. On the other hand looking at the opposite side they can be opportunities for improvement and turn them into new solutions that add value to development and are improved so that it is not so complicated to carry out a change in code.

I’m going to write code using a library developed in Java and you can downloaded from Maven Central: Vavr.io and well you would say that is one Library that has classes focused on Functional Programming, how it would help us to solve some issues related to awful code? Well, this library has some util classes that I will use throughout this post and compare the code with old uses of code.

Using Functional Programming

The Functional Programming is use pure functions (no side effects or change states in objects), write code using this paradigm can be difficult so try to don’t implement everywhere, many applications don’t need to be programming using this paradigm.

I will not deep too much into detail concepts of Functional Programming, so I will only apply those that are presented by the library and that are enough to show examples on how to use them.

What is Vavr?

Vavr (formerly called Javaslang) is a functional library for Java 8+ that provides persistent data types and functional control structures.

With Vavr we can create Java applications using classes that are in the functional programming language.
Note: These examples are used considering that there are no classes that perform the functionality provided by Vavr, if the functionality is available in the most recent versions of Java, it is up to the developer to use the option that suits best.
To start using Vavr I am going to use a maven project and there I will run the code. For create the maven project use the command:

$ mvn archetype:generate \
-DgroupId=fp.samples \
-DartifactId=vavr-fp \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DarchetypeVersion=1.4 \
-DinteractiveMode=false

Change the Java version in pom.

<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>

Add the dependency of Vavr:

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

Immutability

Ensuring that objects do not change their state allows you to work with constant values that can be computed on algorithms and don’t expect undesired results. In Java we already have a famous example such as the String class that when applying operations on String instances these generate a new string object. Now what we need is to apply this same functionality to our classes. For represent this behave I use Tuples.
Tuples are structures that are integrated into the Vavr API. We can represent until 8 elements.

// Declaration:Tuple2<String, Integer> data= Tuple.of("M5", 2000);// Accessing by method.data._1();     // ---> "M5"
data._2(); // ---> 3
// Access using variables.String elm1 = data._1; //---> "M5"
Integer elem2 = data._2; //---> 3

The name of the class followed by the number tells us how many elements are going to be operated and followed by the parameterizable type. The Tuple.of factory method allows us to create a Tuple instance. There are some interest methods that can be util like: swap (invert the order), append (add new element).

Functions

Functions allow us to operate on input data and transform it into output data, it should not have side effects such as exceptions and state changes in objects. Now we will see how to create functions that are already predefined in Vavr and how using them saving us time in their definition and construction, since if you are occupying Java 8 you will realize that we do not have interfaces that support a large number of parameters.

// Supporting us of lambdas, function receives a parameter of type Integer and returns an Integer, the processing only increases the value by 1.Function1<Integer, Integer> addOne = a -> a + 1;

assertEquals(Integer.valueOf(8), addOne.apply(7));
assertEquals(Integer.valueOf(8), addOne.apply(7));

// We can also use method reference, in this case the sum method receives 2 parameters and adds them, on the side of the function definition the first 2 parameters are the input data and the last one is the output type.
Function2<Integer, Integer, Integer> sum = Integer::sum;

assertEquals(Integer.valueOf(10), sum.apply(5, 5));
assertEquals(Integer.valueOf(10), sum.apply(5, 5));

Composition

The interesting part of functions is that we can chained to perform various operations on the data as if they are pipes:

// Declare functions to chain.Function1<Integer, Integer> addOne = a -> a + 1;
Function1<Integer, Integer> multiplyOne = a -> a * 2;

Function1<Integer, Integer> addAndMultiply = addOne.andThen(multiplyOne);

// Add 1 and then multiply by 2.
assertEquals(Integer.valueOf(8), addAndMultiply.apply(3));

Also with the compose method:

// Declare the functions.Function1<Integer, Integer> addOne = a -> a + 1;
Function1<Integer, Integer> multiplyOne = a -> a * 2;

Function1<Integer, Integer> compose = addOne.compose(multiplyOne);

// Multiply by 2 and add 1. As you can see the execution order is inverted when using compose, it starting from last function added.
assertEquals(Integer.valueOf(7), compose.apply(3));

Lifting

The operations that sometimes we need it could present during their execution failures or results that in certain occasions we need to recover and process, to obtain one or another value we will use a lifting:

// Function can return 2 of results.Function2<Integer, Integer, Option<Integer>> div =  Function2.lift((a, b) -> a / b);

// Here the execution could throw ArithmeticException: divide by zero, but we get None object.
Option<Integer> fail = div.apply(100, 0);
assertEquals(Option.none(), fail);

// The execution was successful.
Option<Integer> correct = div.apply(100, 10);
assertEquals(Integer.valueOf(10), correct.get());

Currying

The decomposition of a function allows to reduce the number of parameters as they are obtained, so functions need values that are provided in following lines can be added without affecting the result of the execution.

// Create the function to receive all parameters.Function5<DayOfWeek, DayOfWeek, DayOfWeek, DayOfWeek, DayOfWeek, Map<String, DayOfWeek>> calendar = (d1, d2, d3, d4, d5)-> {
Map<String, DayOfWeek> routine = new HashMap<>();
routine.put("Squat", d1);
routine.put("Plank", d2);
routine.put("Lunge", d3);
routine.put("Push Up", d4);
routine.put("Jumping Jacks", d5);
return routine;
};

// Adding 3 days for the firsts workouts.
Function2<DayOfWeek, DayOfWeek, Map<String, DayOfWeek>> sessionOne = calendar.apply(DayOfWeek.MONDAY,
DayOfWeek.WEDNESDAY,
DayOfWeek.FRIDAY);
// Adding 1 day for the next workout.Function1<DayOfWeek, Map<String, DayOfWeek>> sessionTwo = sessionOne.apply(DayOfWeek.TUESDAY);// Adding the final day.Map<String, DayOfWeek> workoutWeek = sessionTwo.apply(DayOfWeek.THURSDAY);

// Workout for all weekdays.
assertEquals(5, workoutWeek.size());

Try

When exceptions occur in our code, we usually use try-catch blocks, however, it usually happens in the block that corresponds to the catch, we return a value that indicates an error occurred (return -1 or a String “Error”) this will cause that we losing data about the origin of the exception. If we want to keep this processing simple we can use Try class:

try {
// some exception occur.
} catch(Exception e) {
// You can print in console the error
// Or return some result like number value ( -1 )
//
Rethrow the exception ...
throw new Exception("Error");
}

// Trying to avoid side effects like exceptions.

int a = 5;
int b = 0;
// A clear example of divide by zero.Try<Integer> result = Try.of(() -> a / b);
// Obtain the success or failed results.result.
onSuccess(integer -> assertEquals(Integer.valueOf(5), integer));
result.
onFailure(throwable -> System.out.println(throwable));

We can also check if there are any errors when consulting with the Try.Failure() or Try.isSuccess() methods.

Pattern Matching

Pattern matching is very similar to the switch structure only if there is a difference, it can return a value based on the case in which the condition of the expression has matched.

// Using switch statement.String command = null;
int option = 2;
switch (option) {
case 1:
command = "Order a pizza";
break;
case 2:
command = "Order a hamburger";
break;
default:
command = "Order any food";
}
// Using Pattern matching.String cmd = Match(option).of(
Case($(1), "Order a pizza"),
Case($(2), "Order a hamburger"),
Case($(), "Order any food")
)
;
assertEquals("Order a hamburger", cmd);

Memoized

Keep a cache in some operations is useful and thanks to the memorized method we can save some time processing in our application.

Function0<LocalDateTime> fixedDateTime =
Function0.of(LocalDateTime::now).memoized();

// In each invocation get the same value.

LocalDateTime dayAndHour1 = fixedDateTime.apply();
LocalDateTime dayAndHour2 = fixedDateTime.apply();
assertEquals(dayAndHour1, dayAndHour2);

Either

The Either class let to us to get only two values, the left side or right side. Very useful when there are scenarios in database results where exists or not elements.

// Return 2 results.Either<Option<Integer>, Integer> either =
Option.of(5).toEither(() -> Option.none());
if (either.isLeft()) {
assertEquals(Option.none(), either.getLeft());
}
if (either.isRight()) {
assertEquals(Integer.valueOf(5), either.get());
}

Conclusion

Testing new alternatives to improve our code and apply it is a big step to be greater programmers, even the smallest refactor can contribute to making it more understandable for programmers who have to maintain code or who are in design stages. I believe that more than a library or paradigm provides us with useful elements to code better, as developers must improve our algorithms and solutions. I hope this publication helps to take functional programming as an alternative and improve the code either in Java or in some other language that you like the most.

Varv.io

Repository

--

--