Pure functions, High Order functions, Immutability, Composition, Currying, Recursion, Lazy, Functors, Foldable, Applicative, Category, Semi Group, Monoid, Monad.
![]()
Concepts
I won’t write something easier to follow than: http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html
I encourage you to follow it, but for this article I will take some of its examples and just show you how you can do it in Java with Vavr.
All images are courtesy of Aditya Bhargava, the author of the article above.
Functors
When a value is wrapped in a box, you can’t apply a normal function to it

This is where map comes in. map knows how to apply functions to values that are wrapped in a box.

Option.of(2).map(i -> i + 3); // Some(5)
Option.<Integer>of(null).map(i -> i + 5); // NoneA functor is any type that defines how map works.
Here’s what is happening behind the scenes when we write Option.of(2).map(i -> i + 3)

Here’s what is happening behind the scenes when we try to map a function on an empty box

Now, what happens when you apply a function to a list?

List.of(2, 4, 6).map(i -> i + 3); // (5, 7, 9)Functions are also functors!
In Java with vavr map is called compose or andThen
Function1<Integer, Integer> add3 = Function1.of(i -> i + 3);
Function1<Integer, Integer> add2 = Function1.of(i -> i + 2);
List.of(2, 4, 6).map(add3.compose(add2)); // (7, 9, 11)Applicatives
Applicatives are like functors, except that not only the value is being wrapped in a context, but the function to apply to it also.

Monads
Monads apply a function that returns a wrapped value to a wrapped value
Suppose a function named half which only works on even numbers:
Option<Double> half(Double x) {
return x % 2 == 0 ? Option.of(x / 2) : Option.none();
}
But what if we feed it a wrapped value?

This is where bind also called flatMap or sometimes chain comes in!
Option.of(3d).flatMap(this::half); // None
Option.of(4d).flatMap(this::half); // Some(2)
If you pass in None, it’s even simpler:

You can chain calls to flatMap:
Option.of(20d)
.flatMap(this::half) // Some(10)
.flatMap(this::half) // Some(5)
.flatMap(this::half); // None
So now we know that Option is a functor, an applicative and a monad.
Another example: user types a path, we load the file content and display it
Option<String> getLine() {
System.out.print("File: ");
return Option.of(new Scanner(System.in).next());
}
Option<String> readFile(String file) {
return Try.of(() -> new String(Files.readAllBytes(Paths.get(file)))).toOption();
}
Option<Boolean> putStrLn(String str) {
System.out.println("File content:\n");
System.out.println(str);
return Option.of(str.length() % 2 == 0);
}
And just call it like this:
getLine()
.flatMap(this::readFile)
.flatMap(this::putStrLn);
Some more code please!
How to FP in Java with Vavr
- Functions
- Composition
- Lifting
- Partial application
- Currying
- Memoization
- Values
- Option
- Try
- Either
- …
Functions
Java provides only:
Function: accepts one parameterBiFunction: accepts two parameters
Vavr goes up to 8 parameters:
Function0,Function1,Function2, …- Support checked functions:
CheckedFunction1,CheckedFunction2, … - Support composition, lifting, currying and memoization
Function2<Integer, Integer, Integer> sum = (a, b) -> a + b;
sum.apply(40, 2); // 42Composition
Functions can be composed:
f : X -> Yg : Y -Zh : X -> Z = g(f(x))
Use compose or andThen for mor natural (for humans) order.
Function1<String, String> upperCase = Function1.of(String::toUpperCase);
Function1<String, String> hi = s -> "Hi, " + s;
Function1<String, String> addMark = s -> s + " !";
Function1<String, String> welcome =
addMark.compose(hi.compose(upperCase));
welcome.apply("tars"); // Hi, TARS !
Function1<String, String> welcome2 =
upperCase.andThen(hi).andThen(addMark);
welcome2.apply("tars"); // Hi, TARS !Lifting
Partial Functions are functions that are valid for a specific subset of values but may yield errors for some input
The lift function lifts a partial function into a total function
- It can accept all input
- Returns an
Optioninstead of the value - Instead of throwing an exception it will return
None
Function2<Integer, Integer, Integer> divide = (a, b) -> a / b;
Function2<Integer, Integer, Option<Integer>> safeDivide = Function2.lif(divide);
safeDivide.apply(15, 5); // Some(3)
safeDivide.apply(15, 0); // NonePartial application
Partial application allows you to create new function from an existing one by setting some arguments
It is not currying
Function2<Integer, Integer, Integer> multiply = (a, b) -> a * b;
Function1<Integer, Integer> twoTimes = multiply.apply(2);
multiply.apply(4, 3); // 12
twoTimes.apply(9); // 18
Function3<Long, Long, Long, Long> computation = (a, b, c) -> a + (b * c);
Function2<Long, Long, Long> incMultiply = computation.apply(1L);
incMultiply.apply(L2, 3L); // 1 + (2 * 3) = 7Currying
Currying is the same as converting a function that takes n arguments into n functions taking a single argument each.
Function3<Long, Long, Long, Long> computation = (a, b, c) -> a + (b * c);
Function2<Long, Long, Long> incMultiply = computation.apply(1L);
incMultiply.apply(L2, 3L); // 1 + (2 * 3) = 7
Function1<Long, Function1<Long, Function1<Long, Long>>> curriedComputation =
computation.curried();
curriedComputation.apply(1L).apply(2L).apply(3L); // 1 + (2 * 3) = 7Memoization
Memoization acts like some kind of caching, if you memoize a function, it will be only executed once for a specific input
Function0<String> cachedUUID = Function0.of(UUID::randomUUID)
.andThen(UUID::toString).memoized();
cachedUUID.apply(); // 88e93417-2adb-48f0-81f3-aa9bd41efad3
cachedUUID.apply(); // 88e93417-2adb-48f0-81f3-aa9bd41efad3
Function1<String, String> cachedUserUUID =
Function1.of((String user) -> user + ": " + UUID.randomUUID().toString())
.memoized();
cachedUserUUID.apply("Tars"); // Tars: d01562a7-1a13-45c0-b567-3239f4d0abc1
cachedUserUUID.apply("Kipp"); // Kipp: 304b1e2d-4c76-4785-9acc-de04ecf87730
cachedUserUUID.apply("Case"); // Case: 2e77448f-9a4d-4fc1-8b78-8805b035dd5b
cachedUserUUID.apply("Tars"); // Tars: d01562a7-1a13-45c0-b567-3239f4d0abc1Values
Option
Option is a monadic container with additions:
mapflatMapfilterpeek- …
Represents an optional value: None / Some(value).
final String[] robots = new String[] {"Tars", "Kipp", "Case"};
Function0<String> randomRobot = () -> {
Random r = new Random();
boolean shouldFail = r.nextInt(10) > 5;
return shouldFail ? null : robots[r.nextInt(3)];
};
Function1<String, String> upperCase = Function1.of(String::toUpperCase);
Option.of(randomRobot.apply()).map(upperCase); // None
Option.of(randomRobot.apply()).map(upperCase); // Some(KIPP)
Option.of(randomRobot.apply()).map(upperCase); // Some(TARS)
Option.of(randomRobot.apply()).map(upperCase); // Some(TARS)
Option.of(randomRobot.apply()).map(upperCase); // None
Option.of(randomRobot.apply()).map(upperCase); // Some(CASE)Try
Try is a monadic container wich represents a computation that may either throw an exception of successfuly completes
Like Option it has a lot of additions:
mapflatMapfiltermapTrypeekrecoveronFailureonSuccess- …
final String[] robots = new String[] {"Tars", "Kipp", "Case"};
Function0<String> randomRobot = () -> {
Random r = new Random();
boolean shouldFail = r.nextInt(10) > 5;
if (shouldFail)
throw new RuntimeException("Plenty of slaves for my robot colony");
else
return robots[r.nextInt(3)];
};
Function1<String, String> upperCase = Function1.of(String::toUpperCase);
Try.of(randomRobot::apply).map(upperCase); // Failure(Plenty of slaves for my robot colony)
Try.of(randomRobot::apply).map(upperCase); // Some(CASE)
Try.of(randomRobot::apply).map(upperCase); // Some(KIPP)
Try.of(randomRobot::apply).map(upperCase); // Failure(Plenty of slaves for my robot colony)
Try.of(randomRobot::apply).map(upperCase); // Failure(Plenty of slaves for my robot colony)
Try.of(randomRobot::apply).map(upperCase); // Some(CASE)
Try.of(randomRobot::apply).map(upperCase); // Some(TARS)Either
Either represents a value of two types, it is either a Left or a Right.
By convention Left is the failure case, and Right the success case, like Option and Try it has a lot of additions:
rightleftmapflatMapfilter- …
Function0<Either<Throwable, String>> safeRandomRobot =
() -> {
Random r = new Random();
boolean shouldFail = r.nextInt(10) > 5;
return shouldFail ? Either.left(new RuntimeException("Plenty of slaves for my robot colony"))
: Either.right(robots[r.nextInt(3)]);
};
// Plenty of slaves for my robot colony
safeRandomRobot.apply().map(upperCase).getOrElsGet(Throwable::getMessage);
// TARS
safeRandomRobot.apply().map(upperCase).getOrElsGet(Throwable::getMessage);
// TARS
safeRandomRobot.apply().map(upperCase).getOrElsGet(Throwable::getMessage);
// CASE
safeRandomRobot.apply().map(upperCase).getOrElsGet(Throwable::getMessage);
// Plenty of slaves for my robot colony
safeRandomRobot.apply().map(upperCase).getOrElsGet(Throwable::getMessage);
// KIPP
safeRandomRobot.apply().map(upperCase).getOrElsGet(Throwable::getMessage);That’s all for now, I encourage you to try Vavr, it can make your code both cleaner and safer at the same time.
Edit: See also my other article about how to use Try efficiently in the context of a pipeline.
Cheers!