-
-
Notifications
You must be signed in to change notification settings - Fork 639
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add factory method that allows to resolve/reject a Future #1529
Comments
Let's dig deeper into the topic on how the new Example 1: Expensive resolve call, e.g. having a long running operation// starts a new Thread because longRunningCompuration() must not block the current Thread
Future.of(resolve -> resolve.accept(longRunningComputation()));
// uses someExecutor to start a new Thread
Future.of(someExecutor, resolve -> resolve.accept(longRunningComputation())); Example 2: Cheap resolve call, e.g. for internal Future.map implementationFuture<T> first = Future.of(() -> longRunningComputation());
Function<T, U> mapper = ...;
// creates a new Thread for second Future
Future<U> second = Future.of(resolve ->
// onComplete reuses Thread of first Future
first.onComplete(result -> resolve(result.map(mapper))
); Here are the details what happens:
Summed up, the second Future immediately finishes the computation because If we are aware of cheap running operations, we should execute the operation on the current thread: Future<U> second = Future.of(SAME_THREAD_EXECUTOR_SERVICE, resolve ->
first.onComplete(result -> resolve(result.map(mapper))
); This is equivalent (in fact it will be equivalent when we finished #1530) to: Future<U> second = Future.of(SAME_THREAD_EXECUTOR_SERVICE, resolve ->
first.onComplete(SAME_THREAD_EXECUTOR_SERVICE, result -> resolve(result.map(mapper))
); The
The public static final ExecutorService DEFAULT_EXECUTOR_SERVICE = Executors.newCachedThreadPool(); I would not create another class for the Javaslang ExecutorServices. ConclusionIn the case of long running operations, we need to create a new Thread. The golden rule is:
|
SyntaxThis is the suggested new API (and also an interesting example for co-variant Consumers!): interface Future<T> {
// using Future DEFAULT_EXECUTOR_SERVICE
static <T> Future<T> of(CheckedConsumer2<Consumer<? extends T>, Consumer<? extends Throwable>> resolveOrReject);
static <T> Future<T> of(CheckedConsumer<Consumer<? extends T>> resolve, CheckedConsumer<Consumer<? extends Throwable>> reject);
static <T> Future<T> of(CheckedConsumer<Consumer<? extends T>> resolve);
// with explicit ExecutorService
static <T> Future<T> of(ExecutorService executorService, CheckedConsumer2<Consumer<? extends T>, Consumer<? extends Throwable>> resolveOrReject);
static <T> Future<T> of(ExecutorService executorService, CheckedConsumer<Consumer<? extends T>> resolve, CheckedConsumer<Consumer<? extends Throwable>> reject);
static <T> Future<T> of(ExecutorService executorService, CheckedConsumer<Consumer<? extends T>> resolve);
} Note that we do not have Semantics (informal)Without the loss of generality we define the semantics of the following function: interface Future<T> {
static <T> Future<T> of(ExecutorService executorService, CheckedConsumer2<Consumer<? extends T>, Consumer<? extends Throwable>> resolveOrReject) {
final FutureImpl<T> future = new FutureImpl<>(executorService);
future.run(() -> {
final Context<T> context = ... // TBD
final Consumer<T> resolve = result -> context.set(result); // TBD
final Consumer<Throwable> reject = x -> throw x; // TBD
resolveOrReject.apply(resolve, reject));
return context.get();
}
return future;
}
} |
The nature of a PromiseOne might think we loose the ability to pass around a writable object that is able to mutate an underlying Future, e.g. Promise<T> giveMeAPromiseOfTypeT() {
return Promise.make();
} What we gain here is just the type final Promise<T> promise = giveMeAPromise();
mightBeInterestedIn(promise.future());
// zZz
promise.complete(foo()); What's wrong with this code? We pulled the computational logic out of our asynchronous computation context. If we pass the underlying Future around we do even not know if the Future will be completed at all. We could use a Future instead of a Promise: final Future<T> future = Future.of(() -> foo());
mightBeInterestedIn(future); Yes, that's also working. So when do we need to use Promise at all? Promise is a way (/workaround?) to combine Futures. Given a Future (that is not completed yet) we might further process the Future value. The only way to do this is to register a completion handler/action, i.e. with one
But these methods take Consumer functions, they do not return a value. If we want to transform the value we need to return a new Future because the value might not be present yet. Currently we can't express this with our old-fashioned Future API. Instead we use a Promise: <U> Future<U> transform(Future<T> future, Function<T, U> transformation) {
final Promise<U> promise = Promise.make();
future.onComplete(result -> promise.complete(transformation.apply(result)));
return promise.future();
} It has already been determined which transformation should be applied. Our new Future API leverages this property and brings the computation back into the asynchronous computation context: <U> Future<U> transform(Future<T> future, Function<T, U> transformation) {
return Future.of(resolve ->
resolve.accept(transformation.apply(result))
);
} Currently I find no example where it is necessary to provide the |
Hi @danieldietrich, Thanks for inviting me here :) Long brain dump ahead, I hope you can glean any value from it!
Sounds cool :)
What is the definition of heavy here?
Technically, at least in Scala, it is not a wrapper.
This is a statement, but comes across ass criticism. Should it be monadic, what would the value be? The proposed solution is not monadic either?
Could this be solved with better / more documentation? I saw a section on scheduling considerations, and I'd like to recommend the following, from my former colleague @havocp, which has been a great guiding principle to avoid rather nasty surprises in runtime behavior: http://blog.ometer.com/2011/07/24/callbacks-synchronous-and-asynchronous/ I think the idea of providing a completion function is an interesting one: def futureOf[U](f: (Try[U] => Boolean) => Unit)(implicit ec: ExecutionContext): Future[U] = // 1
Future.successful(()).flatMap[U](_ => { // 2
val p = Promise[U]() // 3
f(p.tryComplete _) // 4
p.future // 5
}) Lines explained: 1: The supplied function 2: In Scala 2.12 we could use Future.unit.flatMap instead (cached instance of 3: We create a new promise of the desired result type since we need to transport the values sent to us to the returned Future 4: We ETA-expand the tryComplete function and pass it into 5: we make sure to return the resulting Future to flatMap, carrying the resulting value back to the returned Future of the futureOf method ALTERNATIVELY: def future[T]: ((Try[T] => Boolean), Future[T]) = {
val p = Promise[T]()
(p.tryComplete _, p.future)
} The alternative is arguably much simpler. Promise: The Good & the BadGood:
Bad:
So, I think, my current opinion I that what you propose is a nice, complementary, way of completing Promises. In my experience, I rarely need to deal with Promises directly, but when I do, having the intent which is I hope the Future of your day is great! Cheers, |
Addendum: There are some really interesting news in the Futures for Scala 2.12, which I've described in a blog series here |
Hi @viktorklang, thanks for joining the party and sharing your thoughts! I've followed your links, great stuff (still have to dig deeper regarding space-leaks with ACPS-code - wondering if Java(slang) has the same problem).
so far so good :) Your points and the code examples are very helpful. Coming from // Scala
((T => Boolean, Throwable => Boolean) => Unit // Java
CheckedConsumer2<Predicate<T>, Predicate<Throwable>> your deduction helped me to understand that, what really happens is, we pass a Promise to our Future constructor. Here are the deduction steps: // Scala
(Try[T] => Boolean) => Unit // Java
CheckedConsumer<Predicate<Try<T>>>
This could be addressed in Java by using // Java
@FunctionalInterface
public interface Promise<T> {
boolean tryComplete(Try<T> result);
} Now the type of the function underlines its purpose.
Yep, // Scala
def futureOf[U](f: Promise[U] => Unit)(implicit ec: ExecutionContext): Future[U] =
Future.successful(()).flatMap[U](_ => {
val p = Promise[U]()
f(p)
p.future
}) // Java
static <U> Future<U> of(ExecutorService executorService, CheckedConsumer<Promise<U>> f) {
return Future.successful(null).flatMap(executorService , ignored -> {
final Promise<U> p = Promise.make(executorService);
f.accept(p);
return p.future();
});
} Btw - the trick with the cached instance My original example can then be expressed like this: // Java
return Future.of(promise -> {
onComplete(result -> promise.trySuccess(mapper.apply(result)));
}); (Please note that the example is not very good because it is the body of the I think having such a Future constructor could be helpful. I agree that removing Promise is not a good idea. Thanks again, Viktor. I really love your APIs/your code. In Future/Promise it all boils down to compositional calls on top of a thin, hardened core. |
@danieldietrich Thanks for inviting me! I'll play around with the idea of an alternative Future "factory" when I have some time to focus :)
This. So much. |
@viktorklang Hope this might be useful in some way. Looking forward to hear from the Klang-Meister :) |
@danieldietrich Definitely looks promising—thanks for improving my understanding :-) |
Idea: If class Promise<T> implements Function1<Try<T>, Boolean> {
@Override
public Boolean apply(Try<T> result) {
return tryComplete(result);
}
...
} Note: When introducing a rich Predicate1 in Javaslang the above might be replaced with Our example then looks like this: <U> Future<U> map(Function<? super T, ? extends U> mapper) {
return Future.of(p -> onComplete(p.compose(mapper)));
} In Scala (Hope this is right - writing this from the top of my head while cooking... Needs to be tested...) @viktorklang Having |
As Viktor mention on his blog, Promise might not be used as promised by the user, e.g. Promise<?> promise = Promise.make();
promise.completeWith(promise.future); // 😱 We removed it in #2093 and introduced the equivalent static <T> Future<T> join(CheckedConsumer<Predicate<Try<? extends T>>> computation) { ... } As the name suggests, Example: // new API
Future<String> greeting(Future<String> nameFuture) {
return Future.join(tryComplete -> {
nameFuture.onComplete(name -> tryComplete.test(Try.success("Hi " + name)));
});
}
// old API
Future<String> greeting(Future<String> nameFuture) {
final Promise promise = Promise.make();
nameFuture.onComplete(name -> promise.tryComplete(Try.success("Hi " + name)));
return promise.future();
} |
In ES6 we use Promise like this:
We aligned to Scala when creating Future (for read operations) and Promise (for write operations). But Promise looks too heavy. It is a wrapper around a Future that provides statements to mutate the underlying Future. Promise is not monadic opposed to Future.
When starting async programming with Scala it was first hard for me to get the difference between Future and Promise and when to use which.
I think we could greatly simplify the API by providing ES6 like factory methods and first deprecate, then remove Promise completely (in the next major release).
We write
instead of
Note: there will be different factory methods with and without reject function, having BiConsumer, two Consumers or one Consumer, with and without ExecutorService. Examples will follow.
The text was updated successfully, but these errors were encountered: