SkillAgentSearch skills...

Applicatives

Java code generation for applicative functors, selective functors and more

Install / Use

/learn @wernerdegroot/Applicatives
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Applicatives

Easily combine CompletableFutures, Lists, Functions, Predicates and even your own data types!

Check out reading and writing JSON to get a feel for the power of the paradigm.

Table of contents

Getting started

Java 8 or higher is required.

Add the required dependencies:

Example:

<dependencies>
  <dependency>
      <groupId>nl.wernerdegroot.applicatives</groupId>
      <artifactId>runtime</artifactId>
      <version>1.2.1</version>
  </dependency>
</dependencies>

...

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>${maven.compiler.source}</source>
                <target>${maven.compiler.target}</target>
                <annotationProcessorPaths>
                    <path>
                        <groupId>nl.wernerdegroot.applicatives</groupId>
                        <artifactId>processor</artifactId>
                        <version>1.2.1</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

You may also want to include prelude, for applicative instances for some common classes that are included in Java's standard library:

<dependency>
    <groupId>nl.wernerdegroot.applicatives</groupId>
    <artifactId>prelude</artifactId>
    <version>1.2.1</version>
</dependency>

Motivating example

Combining two CompletableFutures

Suppose you have a class like the following:

public class Person {
    
    private final String firstName;
    private final String lastName;

    public Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    // Getters, `hashCode`, `equals` and `toString`
}

Let's pretend it will take some time for the application to come up with a firstName and a lastName. Perhaps you need to load those from a slow database, or make a network request:

// Get a first name:
String firstName = "Jack";

// Wait a while and get a last name:
TimeUnit.HOURS.sleep(24);
String lastName = "Bauer";

// Combine the first name and last name into a `Person`:
Person person = new Person(firstName, lastName);

Instead of blocking the main thread of your application, you decide to switch to non-blocking CompletableFutures. Although it will take some time to obtain firstName and lastName and combine those into a Person, the application is free to perform other, useful tasks while it waits:

// Get a first name:
CompletableFuture<String> futureFirstName = 
        CompletableFuture.completedFuture("Jack");

// Wait a while and get a last name:
CompletableFuture<String> futureLastName = 
        CompletableFuture.supplyAsync(() -> {
            TimeUnit.HOURS.sleep(24);
            return "Bauer";
        });

// Combine the two `CompletableFuture`s with a first name 
// and a last name into a `CompletableFuture` with a `Person`:
CompletableFuture<Person> futurePerson =
        futureFirstName.thenCombine(futureLastName, Person::new);

// Do something useful while we wait for the `Person`. 

The method thenCombine is a nifty function that combines the result of two CompletableFutures (by using a BiFunction that you provide). In this case, the provided BiFunction is the Person's constructor that combines a first name and a last name into a Person object.

The method thenCombine is very useful, but unfortunately it will only work on two CompletableFutures at a time. The next section describes the problem of combining three or four CompletableFutures, and shows how to solve this problem using the Java standard library. The solution should leave you somewhat dissatisfied. The section will continue by showing you how you can use this library to reduce the amount of boilerplate to a minimum.

Combining more CompletableFutures

Suppose you work on an application that allows people to trade Pokemon cards online. Such an application might have a PokemonCard class like the following:

public class PokemonCard {
   
    private final String name;
    private final int hp;
    private final EnergyType energyType;
    private final List<Move> moves;
    
    public PokemonCard(String name, int hp, EnergyType energyType, List<Move> moves) {
        this.name = name;
        this.hp = hp;
        this.energyType = energyType;
        this.moves = moves;
    }

    // Getters, `hashCode`, `equals` and `toString`
}

Imagine that each of the attributes of such a PokemonCard need to be loaded from some different external system (perhaps your company is using microservices). Instead of having a String, int, EnergyType and List<Move>, which can be combined directly into a PokemonCard using the PokemonCard's constructor, you are stuck with a bunch of CompletableFuture's that you can't combine directly:

// Fetch the name of the Pokemon:
CompletableFuture<String> futureName = ...;

// Fetch the number of health points: 
CompletableFuture<Integer> futureHp = ...;

// Fetch the energy type of the card: 
CompletableFuture<EnergyType> futureEnergyType = ...;

// Fetch the moves the Pokemon can perform: 
CompletableFuture<List<Move>> futureMoves = ...;

How do you combine those?

Like I claimed in the previous section, thenCombine won't be of much help. It's capable of combining two CompletableFutures, but not any more than that. Unfortunately, the authors of the Java standard library did not provide an overload for thenCombine that combines four CompletableFutures.

If you are willing to go through the hassle, you can still use thenCombine to combine these four CompletableFutures. You'll have to call that method no less than three times: once to combine futureName and futureHp into a CompletableFuture of some intermediate data structure (let's call it NameAndHp), then again to combine that with futureEnergyType into a CompletableFuture of yet another intermediate data structure (named something like NameAndHpAndEnergyType), and one last time to combine that with futureMoves into a CompletableFuture of a PokemonCard. This is obviously not a solution for a programmer that demands excellence from their programming language!

The best alternative I found is to abandon thenCombine completely and wait until all CompletableFutures are resolved using CompletableFuture.allOf. We can then use thenApply and join to extract the results (which requires some care, as you may accidentally block the main thread if the computation did not complete yet):

CompletableFuture<PokemonCard> futurePokemonCard = 
        CompletableFuture.allOf(
                futureName,
                futureHp,
                futureEnergyType,
                futureMoves
        ).thenApply(ignored -> {
            String name = futureName.join();
            int hp = futureHp.join();
            EnergyType energyType = futureEnergyType.join();
            List<Move> moves = futureMoves.join();

            return new PokemonCard(name, hp, energyType, moves);
        });

There are several other ways of achieving the same result. See StackOverflow for a discussion about the trade-offs on each of these alternatives.

Instead of having to write all this boilerplate code, wouldn't it be nice if the authors of Java's standard library would just provide a couple of overloads for thenCombine for three or more CompletableFutures? Even though the Java standard library doesn't have such a thing, this library has your back!

Using the library (with CompletableFutures)

All that is required of you is to write a method to combine two CompletableFutures, and annotate that with @Covariant. We write:

public class CompletableFutures {

    @Covariant
    public <A, B, C> CompletableFuture<C> combine(
            CompletableFuture<A> left,
            CompletableFuture<B> right,
            BiFunction<? sup
View on GitHub
GitHub Stars25
CategoryDevelopment
Updated9mo ago
Forks0

Languages

Java

Security Score

82/100

Audited on Jun 9, 2025

No findings