Hi, I'm currently trying to map a Collection of Futures of different type, to a Collection of its results, while preserving its type information. The only structure I came across that preserves type information is the Tuple, so I did something like this (which works by the way)
public Result getResult(){
return Tuple.of(
Future.of(() -> new Type1()),
Future.of(() -> new Type2()),
Future.of(() -> new Type3())
).map(this::executeFutures).apply(Result::new);
}
private Tuple3<Type1, Type2, Type3> executeFutures(Future<Type1> type1, Future<Type2> type2, Future<Type3> type3) {
return Tuple.of(type1.get(), type2.get(), type3.get());
}
Lets assume Result is a class which receives (Type1, Type2, Type3) on its constructor
public class Result {
private Type1 type1;
private Type2 type2;
private Type3 type3;
public Result(Type1 type1, Type2 type2, Type3 type3){
this.type1 = type1;
this.type2 = type2;
this.type2 = type3;
}
}
What bothers me is having to use the "executeFutures" function. It would be nice if I could do something like:
public Result getResult(){
return Tuple.of(
Future.of(() -> new Type1()),
Future.of(() -> new Type2()),
Future.of(() -> new Type3())
).mapEach(Future::get).apply(Result::new);
}
Is there another way to achieve the same thing I want to perform (given a collection of futures of different types, obtain another collection with its results mantaining its types)?
Thanks!
@ggalmazor Any ideas on how to solve this issue?
Hi @santiagopoli,
thank you for your question, this is an interesting use-case!
Generally, using get() is not a good idea because
Currently there is no better solution than using flatMap/map:
public Result getResult() {
final Future<Type1> f1 = Future.of(() -> new Type1());
final Future<Type2> f2 = Future.of(() -> new Type2());
final Future<Type3> f3 = Future.of(() -> new Type3());
return f1.flatMap(t1 -> f2.flatMap(t2 -> f3.map(t3 -> new Result(t1, t2, t3))).get();
}
I think we can do better. Towards 2.1.0 we will enhance our For-comprehension (see #1629). Then we are able to express it like this:
public Result getResult() {
return For(
Future.of(() -> new Type1()),
Future.of(() -> new Type2()),
Future.of(() -> new Type3())
).yield(Result::new).get();
}
But we still need some time to get 2.1.0 finished. Don't expect it to come near-time.
I hope that helps.
- Daniel
@santiagopoli @ggalmazor @pasku I updated this post. Before, my getResult() methods returned a Future<Result>, therefore I added a get().
Personally I like to return a Future and use the onCompleted(), onSuccess() and onFailure() handlers.
I understand that in Java you can't define a type for the mapper that complies with T, U and V at the same time. With Javaslang's Tuple's map() fn you can do this:
class Result<T, U, V> {
private final T t;
private final U u;
private final V v;
Result(Tuple3<T, U, V> t) {
this.t = t._1;
this.u = t._2;
this.v = t._3;
}
}
public Result<String, Integer, Boolean> getResult() {
return new Result<>(Tuple.of(
Future.of(() -> "cocotero"),
Future.of(() -> 1),
Future.of(() -> true)
).map(Future::get, Future::get, Future::get));
}
It's more verbose, but this lets Java bind the types for each mapper according to the Tuple's types.
I'm sure that @danieldietrich will be able to throw more light into this... It's always interesting to think about this kind of problems: container types (Future) inside container types (Tuple).
@danieldietrich @pasku @santiagopoli following what's been said here... In JavaScript I'd use a barrier to wait for all 3 promises to be resolved and then build my Result instance:
class Result {
// blablabla
static of([value1, value2, value3]) { // ES6 array decomposition
return new Result(value1, value2, value3);
}
}
q.all([promise1, promise2, promise3])
.then(Result.of)
Following this idea:
1 - Is there any kind of barrier mechanism available for Futures in Javaslang?
2 - Could we leverage Javaslang's Pattern Matching to solve this problem (destructuring the array of results of each Future)? Does this make any sense?
edit: Depending on the promises library that you use, you could even do this:
q.all([promise1, promise2, promise3])
.spread((value1, value2, value3) => new Result(value1, value2, value3))
Here there is no need for array destructuring because .spread() operator takes care of it for you. I understand that this is almost the same that will happen with the new For comprehension in Javaslang 2.1.0
@ggalmazor
It's always interesting to think about this kind of problems: container types (Future) inside container types (Tuple).
I think it is technically not possible to calculate an 'upper bound' of the component types of a Tuple.
It is only possible to calculate an upper bound if we provides types at the method level (but that makes no sense in our context):
public <T, T1 extends T, ... Tn extends T> Seq<T> toSeq(T1 t1, ..., Tn tn) { ... }
(See An Ingenious Workaround to Emulate Union Types in Java)
Following this idea:
1 - Is there any kind of barrier mechanism available for Futures in Javaslang?
2 - Could we leverage Javaslang's Pattern Matching to solve this problem (destructuring the array of results of each Future)? Does this make any sense?
Ad 1) The existing barrier mechanisms require a common type T:
Given the toSeq() example (see above) we are able to create a barrier function that returns the _most common type_ T of heterogenous Futures T1..Tn. Java's CompletableFuture does return Object.
Ad 2) I see no way pattern matching could help here. But I think the new For-comprehension methods I mentioned above are great to solve this issue.
Update: Pattern matching should work similar to the For-comprehensions in this context but we need an additional Tuple wrapper:
final Tuple3<Future<T1>, Future<T2>, Future<T3>> tuple = ...;
Match(tuple).of(
Case(Tuple3(Future($()), Future($()), Future($())), (f1, f2, f3) -> ...)
);
@ggalmazor I also updated this post. The pattern matching example was wrong. Javaslang currently has no patterns for Futures.
On the left hand of the Case statement we match the Futures: Tuple3(Future($()), Future($()), Future($())).
On the right hand we get only the first layer of the object tree, namely the futures. But what we really want is the values of the Futures.
So in the end pattern matching does not help us here. For-comprehensions are better.
I will close this issue because I think we can't add new API beside the upcoming For-comprehensions.
Of course the issue is still open for discussion!
Most helpful comment
Hi @santiagopoli,
thank you for your question, this is an interesting use-case!
Generally, using get() is not a good idea because
Currently there is no better solution than using flatMap/map:
I think we can do better. Towards 2.1.0 we will enhance our For-comprehension (see #1629). Then we are able to express it like this:
But we still need some time to get 2.1.0 finished. Don't expect it to come near-time.
I hope that helps.
- Daniel
@santiagopoli @ggalmazor @pasku I updated this post. Before, my getResult() methods returned a Future<Result>, therefore I added a
get().Personally I like to return a Future and use the onCompleted(), onSuccess() and onFailure() handlers.