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…
Advertisement
Answer
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))); }
Explanation of the types
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 types is not exactly expressed by Java interfaces.
Note on the Haskell code in the question
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.