Applicative Functors for the Java Programmer

Naveen Muguda
5 min readAug 16, 2019

Of the many powerful and composable abstractions of the functional programming paradigm, applicative functors don’t get the attention they deserve.

Programmers like me who got introduced to the functional style via Streams in Java will have a bigger disconnect compared to abstractions like Functors and Monads. While the streams in Java are functors and monads, they are not applicatives[I will use Applicatives and Applicative Functors interchangeably].

In this post, I will demonstrate the concepts related to Applicative functor via Java code examples and suggest areas where they are relevant/best suited.

Effects

One of the primary concepts of functional programming is of “Effects”[ not to be confused with “Side Effects”]. Informally, they are properties acquired by placing data into containers.

They have a shape of F[A], where A is the type of the data, while F is a container which provides the effect. Different containers provide different effects. For e.g. Optional provides the effect of null safety, for data placed in it.

Optional.ofNullable(x)
.map(f)
.flatmap(g)
.orElse(x1)

in the above show snippet, NullPointerExceptions are avoided, even if x, or the f(x), or g(f(x)) were null and attempt to deference nulls in f or g.

The first container we will use in this post is List and the effect it provides is ordered storage of many of a type.

Partial application and Currying

Partial application refers to the act of converting a function of n(> 1) arguments into one having less than n arguments. This can be done generically*.

public static <A, B, C> Function<B, C> apply(BiFunction<A, B, C> biFunction, A a){
return b-> biFunction.apply(a, b);
}

consider the following function sum, which has two arguments

public static int sum(int first, int second){
return first + second;
}

by partially applying you can construct a function sum2, which accepts a single parameter.

Function<Integer, Integer> sum2 = apply(sum, 2);
sum2.apply(3);

when sum2 is invoked with 3, it will return a value of 5.

The next mechanism we need to understand is of currying. It can be understood as f(x,y,z) -> f(x)(y)(z). i.e. applying x will result in a function, which when provided y will give yet another function which accepts z.

The following is a generic version of this currying

public static <A, B, C> Function<A, Function<B, C>> curry(BiFunction<A, B, C> biFunction){
return a -> apply(biFunction, a);
}

sum can be curried as follows

curry(sum).apply(2)
.apply(5);

Applicative Functors are Functors. Here’s a very short description of Functors, They are containers that can be mapped over. If a list had two Strings “Hi” “World” and if the function String::length is mapped over, it results in a list whose contents are [2, 5]

here is the code

public class List<E>  {

private final java.util.List<E> data;

public List(java.util.List data) {
this.data = data;
}

public <F> List<F> map(Function<E, F> function){
return new List<>(data.stream().map(function).collect(Collectors.toList()));
}
}

A container like List which provides an “effect” can provide other capabilities one such capability is of applicatives.

The following is the signature of an Applicative in a language like Scala, which supports Higher Kinded Types.

trait Applicative[F[_]] extends Functor[F]
{
def ap[A, B](ff: F[A => B])(fa: F[A]): F[B]
def pure[A](a: A): F[A]
def map[A, B](fa: F[A])(f: A => B): F[B] = ap(pure(f))(fa)
}

Since Java doesn’t support Higher Kinded Types, let’s define the signature of ap function as

class List<E> {
...
List<F> ap (List<Function<E, F>> arg);
}

defining the signature is easy, the harder part is defining the behavior of the function.

The behavior of the ap function is “it applies a list of functions to the list of Es and stores them in order in the resulting list. Here is a sample implementation.

public <F> List<F> ap(List<Function<E, F>> list){
java.util.List<F> result = new ArrayList<>();
for(Function<E, F> f : list.data){
for(E e:data){
result.add(f.apply(e));
}
}
return new List<>(result);
}

let’s test this code

java.util.List<Integer> integers = new ArrayList<>();
integers.add(1);
integers.add(2);
List<Integer> list = new List<>(integers);


java.util.List<Function<Integer, Integer>> functionList = new ArrayList<>();
functionList.add(x-> x+ 1);
functionList.add(x -> x - 1);
list.ap(new List<>(functionList))
.data.forEach(System.out::println);

that will produce [2 3 0 1]. Neat, isn’t it?

while the above code showed the “ap” of functions of one parameter, interesting use cases emerge when used for functions of higher cardinality.

let’s consider the scenario where we want to find the cross product of two lists, we can use ap function with the currying mechanism shown earlier.

java.util.List<Integer> integers = new ArrayList<>();
integers.add(1);
integers.add(2);
List<Integer> list = new List<>(integers);
list.ap(list.ap(List.of(curry(Tuple::of))))
.data.forEach(System.out::println);

will produce

(1, 1)
(1, 2)
(2, 1)
(2, 2)

Let’s see how “applicativeness” works with other effects

let’s consider an eCommerce site, the price of an item for a user is based on the set of promotions on the item and the user’s past purchases and these are retrieved by using promises(a.k.a futures, CompletionStages)

public Promise<Integer> price(String userId, String itemId){
Promise<User> userPromise = ...;
Promise<Promotion> promotionPromise = ..;
return promotionPromise.ap(userPromise.ap(new Promise<> . (curry(this::price))));

}
public Integer price(User user, Promotion promotion){
..
}

By using applicatives we can parallelize the fetch of User and Promotion.

if we had instead taken the route of monads, we would have to sequence the operations, losing the benefits of parallelization.

Promise<User> userPromise = ...;


userPromise.flatmap(user -> getPromotion(itemId).map(promotion -> Tuple.of(user, promotion)))
.map(tuple ->price(tuple._1(), tuple._2()));

Another poster use case of applicatives is validation.

Consider the scenario where a user object consists of two fields, name and email.

public class User {
private String name;
private String email;
public User(String name, String email) {
this.name = name;
this.email = email;
}
}

and there are rules which determine if name and email are conformant to certain policies.

given a name and an email, we would like to capture the violations of both name and email. By modeling Validation as an applicative, we can perform checks on both fields and conditionally create a User or generate a comprehensive set of error messages. Validation follows the Railway Oriented Programming principles and the unhappy rail gathers error messages from each validation failure.

public Validation<Seq<String>, User> validateUser(
String name, String email) {
final BiFunction<String, String, User> aNew = User::new;
validateField(name, NAME_PATTERN, NAME_ERROR).ap( Validation.valid(curry(aNew)))
.ap(validateField(email, EMAIL_PATTERN, EMAIL_ERROR)))
}
private Validation<String, String> validateField
(String field, String pattern, String error) {

return CharSeq.of(field)
.replaceAll(pattern, "")
.transform(seq -> seq.isEmpty()
? Validation.valid(field)
: Validation.invalid(error));
}

as an exercise attempt to write similar validation in the Object-Oriented or Procedural Style.

Applicatives are a friendlier alternative to monads in some scenarios like the promise parallelization and elaborate validation. Knowledge of applicatives can help programmers pick the right tool for the job, I hope this post provided a helpful introduction to Applicatives.

--

--