Applicative Functors

Applicative functors, or simply applicatives, are a powerful concept in functional programming. They extend the capabilities of functors, which are structures that can be mapped over. Applicatives allow you to apply functions to values inside a context, such as Optional, Stream, or CompletableFuture.

The key characteristic of applicatives is that they provide a way to combine multiple values within a context using a function. This is achieved through the ap (apply) method, which takes a function wrapped in the context and applies it to a value wrapped in the same context.

Here’s an example of using an applicative with Optional:

Optional<Integer> x = Optional.of(5);
Optional<Integer> y = Optional.of(3);
Optional<BiFunction<Integer, Integer, Integer>> sum = Optional.of((a, b) -> a + b);

Optional<Integer> result = sum.flatMap(f -> x.flatMap(a -> y.map(b -> f.apply(a, b))));
// result = Optional[8]

In this example, we have two Optional values, x and y, and a function sum wrapped in an Optional. We use flatMap and map to apply the sum function to the values inside the Optional context, resulting in a new Optional containing the sum.

Function Composition

Function composition is a fundamental concept in functional programming. It allows you to combine multiple functions together to create a new function. In Java, you can achieve function composition using the compose and andThen methods provided by the Function interface.

Here’s an example of function composition:

Function<Integer, Integer> multiplyByTwo = x -> x * 2;
Function<Integer, Integer> addThree = x -> x + 3;

Function<Integer, Integer> composedFunction = multiplyByTwo.compose(addThree);
int result = composedFunction.apply(5);
// result = 16

In this example, we have two functions, multiplyByTwo and addThree. We use the compose method to create a new function composedFunction, which first applies addThree and then applies multiplyByTwo to the result. Applying composedFunction to the value 5 yields the result 16.

Monads

Monads are another powerful concept in functional programming. They provide a way to chain operations together while abstracting away the details of the underlying computation. In Java, examples of monads include Optional, Stream, and CompletableFuture.

Monads are defined by three key operations: of (or unit), map, and flatMap (or bind). The of operation wraps a value inside the monad, map applies a function to the value inside the monad, and flatMap allows you to chain monadic operations together.

Here’s an example of using monads with Optional:

Optional<String> name = Optional.of("Alice");
Optional<Integer> age = Optional.of(25);

Optional<String> greeting = name.flatMap(n ->
    age.map(a -> "Hello, " + n + "! You are " + a + " years old."));
// greeting = Optional["Hello, Alice! You are 25 years old."]

In this example, we have two Optional values, name and age. We use flatMap and map to combine the values inside the Optional context and create a greeting string. The resulting Optional contains the greeting message.