Lifting Functions to Work With Java Monads
Want to take a more functional approach to Java? The language doesn't offer a lot of options, but here is some custom code you can use for method lifting.
Join the DZone community and get the full member experience.
Join For FreeStream and Optional classes, added to Java 8, allow you to have some fun with functional programming. The problem is that Java is still missing quite a lot to be taken seriously as a functional programming language. Lambda notation and two monads (Optional and Stream) are just the tip of the iceberg. This leads to libraries like vavr or functionaljava — both deriving from the purely functional language Haskell.
One of the first things you need to get rid of when trying to be more functional is the attempt to unwrap monads too early. It usually involves using methods like Optional.get() or Stream.collect() where there is no need yet. Sometimes, though, Java doesn't help with that, so let me give you some custom code. This article will bring you closer to the concept of method lifting.
Calculations on Optionals
Suppose we have a nice API we would like to use to calculate numbers:
public interface Math {
int multiply(int a, int b);
double divide(int a, int b);
..
}
We would like to use it to do some calculations on numbers wrapped with Optional:
public interface NumberProvider {
Optional<Integer> getNumber();
}
Let's say we want to write a method, which returns the result of the division of two numbers wrapped with Optional, or empty Optional if any one of them is empty (we skip the zero divisor case here). It may look something like this:
public Optional<Double> divideFirstTwo(NumberProvider numberProvider, Math math) {
Optional<Integer> first = numberProvider.getNumber();
Optional<Integer> second = numberProvider.getNumber();
if(first.isPresent() && second.isPresent()) {
double result = math.divide(first.get(), second.get());
return Optional.of(result);
} else {
return Optional.empty();
}
}
That's rather nasty. It involves a lot of code, the only purpose of which is to wrap/unwrap the Optional. Let's try to make it more functional:
public Optional<Double> divideFirstTwo(NumberProvider numberProvider, Math math) {
return numberProvider.getNumber()
.flatMap(first -> numberProvider.getNumber()
.map(second -> math.divide(first, second)));
}
That's much better. It turns out that invoking flatMap on the first monad and map on the second one inside the lambda can be extracted even further to the method-called lift:
public interface Optionals {
static <R, T, Z> BiFunction<Optional<T>, Optional<R>, Optional<Z>> lift(BiFunction<? super T, ? super R, ? extends Z> function) {
return (left, right) -> left.flatMap(leftVal -> right.map(rightVal -> function.apply(leftVal, rightVal)));
}
}
The lift is able to promote any function, which takes two arguments, to the function with the arguments and the result type wrapped with Optional. It actually adds Optional behavior to the function in such a way that if one of the arguments is empty, then the result will also be empty. If the JDK extracted flatMap and map methods to some common interface, for example, Monad, then we could write one lift function for every instance of a Java monad (Stream, Optional, custom classes). Unfortunately, we need to do this copy-pasting for every instance. The final code for divideFirstTwo becomes:
import static com.ps.functional.monad.optional.Optionals.lift;
...
public Optional<Double> divideFirstTwo(NumberProvider numberProvider, Math math) {
return lift(math::divide).apply(numberProvider.getNumber(), numberProvider.getNumber());
}
Summary
I hope this article encouraged you to play with functional style in Java. The JDK needs to be greatly improved for the language to be called functional in the future. Unfortunately, Java 9 doesn't promise any major improvements besides a few additional methods. The source code can be found here.
Published at DZone with permission of Paweł Szeliga. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments