I'm trying to avoid nesting CompletableFuture
when combining 2 independent ones with a BiFunction
that returns a third one.
Currently, using thenCombine()
does not cut it:
// What I have
public CompletableFuture<CompletableFuture<C>> doStuff() {
CompletableFuture<A> aFuture = makeSomeA();
CompletableFuture<B> bFuture = makeSomeB();
CompletableFuture<CompletableFuture<C>> cFuture = aFuture.thenCombine(bFuture, this::makeSomeC);
return cFuture;
}
// What I want
public CompletableFuture<C> doStuff() {
CompletableFuture<A> aFuture = makeSomeA();
CompletableFuture<B> bFuture = makeSomeB();
// obv this method does not exist
CompletableFuture<C> c = aFuture.thenBicompose(bFuture, this::makeSomeC);
}
private CompletableFuture<A> makeSomeA() {...}
private CompletableFuture<B> makeSomeB() {...}
private CompletableFuture<C> makeSomeC(A a, B b) {...}
I'm basically trying to find a way that looks like haskell if there was a CompletableFuture
monad:
doStuff :: CompletableFuture C
doStuff = do
a <- makeSomeA
b <- makeSomeB
makeSomeC a b
makeSomeA :: CompletableFuture A
makeSomeB :: CompletableFuture B
makeSomeC :: A -> B -> CompletableFuture C
I read somewhere that join()
is the flatMap
of Completable future, so I think I could use this method to do something like aFuture.thenCombine(bFuture, ((Function<CompletableFuture<C>,C>) CompletableFuture::join).compose(this::makeSomeC)
but I'm not sure this is the proper/advised way to go. And I cannot say that this help readability in any way...
To me it looks like thenCompose
is the direct equivalent of the Haskell Monad.bind
.
thenCompose
can be nested in the same way as Haskell monad bind, which is also the result of a do-expression in Haskell. Using that your problem can be solved like this:
public CompletableFuture<C> doStuff() {
CompletableFuture<A> aFuture = makeSomeA();
CompletableFuture<B> bFuture = makeSomeB();
return aFuture.thenCompose(a -> bFuture.thenCompose(b -> makeSomeC(a, b)));
}
This can be seen by inspecting the types of the functions.
Monad bind -- which is written >>=
in Haskell -- has the following type:
(>>=) :: Monad m => m a -> (a -> m b) -> m b
thenCompose
in Java has the following signature:
public <U> CompletionStage<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn)
The above converted to Haskell syntax, with an extra parameter as an explicit this
, looks like this:
thenCompose :: CompletionStage T -> (T -> CompletionStage U) -> CompletionStage U
We can see that this has the same structure as the Haskell type. The difference is the names, and the fact Haskell's support for higher-kinded is not exactly expressed by Java interfaces.
But I am a bit puzzled by your Haskell code. To me it looks like your Haskell code is doing the following:
public CompletableFuture<C> doStuff() {
return makeSomeA().thenCompose(a -> makeSomeB().thenCompose(b -> makeSomeC(a, b)));
}
That is, waiting until the makeSomeA
operation has completed before starting on makeSomeB
. The Java code on the other hand starts the two operations in parallel, then waits for the result before starting on C. But maybe it is a laziness thing.
I guess the simplest solution is just to apply a thenCompose(identity())
afterwards:
public CompletableFuture<C> doStuff() {
CompletableFuture<A> aFuture = makeSomeA();
CompletableFuture<B> bFuture = makeSomeB();
CompletableFuture<CompletableFuture<C>> cFuture = aFuture.thenCombine(bFuture, this::makeSomeC);
return cFuture.thenCompose(Function.identity());
}
Alternatively, introduce a simple Pair
class to combine the results of A and B and use thenCompose()
:
public CompletableFuture<C> doStuff() {
CompletableFuture<A> aFuture = makeSomeA();
CompletableFuture<B> bFuture = makeSomeB();
CompletableFuture<Pair<A, B>> cFuture = aFuture.thenCombine(bFuture, Pair::new);
return cFuture.thenCompose(p -> makeSomeC(p.a, p.b));
}
private static class Pair<A, B> {
A a;
B b;
public Pair(A a, B b) {
this.a = a;
this.b = b;
}
}
And a third alternative without Pair
:
public CompletableFuture<C> doStuff() {
CompletableFuture<A> aFuture = makeSomeA();
CompletableFuture<B> bFuture = makeSomeB();
CompletableFuture<Void> cFuture = CompletableFuture.allOf(aFuture, bFuture);
return cFuture.thenCompose(__ -> makeSomeC(aFuture.join(), bFuture.join()));
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With