
com.github.tonivade.purefun.concurrent.Future Maven / Gradle / Ivy
Show all versions of purefun-core Show documentation
/*
* Copyright (c) 2018-2024, Antonio Gabriel Muñoz Conejo
* Distributed under the terms of the MIT License
*/
package com.github.tonivade.purefun.concurrent;
import static com.github.tonivade.purefun.core.Function1.cons;
import static com.github.tonivade.purefun.core.Function1.identity;
import static com.github.tonivade.purefun.core.Precondition.checkNonNull;
import static java.util.concurrent.CompletableFuture.supplyAsync;
import java.time.Duration;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import com.github.tonivade.purefun.HigherKind;
import com.github.tonivade.purefun.Kind;
import com.github.tonivade.purefun.core.Bindable;
import com.github.tonivade.purefun.core.CheckedRunnable;
import com.github.tonivade.purefun.core.Consumer1;
import com.github.tonivade.purefun.core.Function1;
import com.github.tonivade.purefun.core.Function2;
import com.github.tonivade.purefun.core.Matcher1;
import com.github.tonivade.purefun.core.Producer;
import com.github.tonivade.purefun.core.Tuple2;
import com.github.tonivade.purefun.core.Unit;
import com.github.tonivade.purefun.data.ImmutableList;
import com.github.tonivade.purefun.data.Sequence;
import com.github.tonivade.purefun.type.Try;
import com.github.tonivade.purefun.type.TryOf;
/**
* This type is an abstraction of a computation executed in another thread. To run the computation an {@code Executor}
* should be provided. If no {@code Executor} is provided, then a default instance is used.
*
* The result of the computation is a {@code Try}, this means that the computation can end successfully or with an error.
*
* You can create instances of {@code Future} in this way:
*
* - Future.success(value): returns a future that returns successfully with the given value.
* - Future.failure(error): returns a future that returns an error with the given error.
* - Future.task(computation): returns a future that eventually will execute the given computation.
* - Future.async(consumer): returns a future that eventually will consume the result of a computation.
* - Future.exec(runnable): returns a future that eventually will execute the given runnable.
* - Future.delay(duration, computation): returns a future that eventually will execute the given computation, but after waiting the given duration.
* - Future.defer(computation): returns a future that eventually will execute the given computation that returns another Future.
* - Future.later(computation): returns a future that eventually will execute the given computation that returns another value.
* - Future.bracket(acquire, usage, release): returns a future that eventually will acquire a resource, then use it, and finally release it.
*
*
* A future can be cancellable by calling the method {@code cancel}. If the future has not been executed yet, the future will be cancelled
* and the result of the computation will be a {@code Try.failure(CancellableException)}, but if the future has been executed, and is completed
* the calling of cancel method will not have any consequences. If the computation is running when the cancel method is called, and if the flag
* mayInterruptThread is true, then it will try to interrupt the thread running the computation and the result of the computation
* will be also a {@code Try.failure(CancellableException)}.
*
* If during the execution of the computation in a thread, this thread is interrupted for any reason, the result
* of the computation will be a {@code Try.failure(InterruptedException)}.
*
* @param result of the computation
* @see Try
* @see Promise
*/
@HigherKind
public sealed interface Future extends FutureOf, Bindable, T> {
Executor DEFAULT_EXECUTOR = Executors.newVirtualThreadPerTaskExecutor();
Try await();
Try await(Duration timeout);
void cancel(boolean mayInterruptThread);
boolean isCompleted();
boolean isCancelled();
Future onSuccess(Consumer1 super T> callback);
Future onFailure(Consumer1 super Throwable> callback);
Future onComplete(Consumer1 super Try extends T>> callback);
@Override
Future map(Function1 super T, ? extends R> mapper);
Future mapError(Function1 super Throwable, ? extends Throwable> mapper);
@Override
Future flatMap(Function1 super T, ? extends Kind, ? extends R>> mapper);
Future andThen(Future extends R> next);
Future ap(Future> apply);
Future filter(Matcher1 super T> matcher);
default Future filterNot(Matcher1 super T> matcher) {
return filter(matcher.negate());
}
Future orElse(Future extends T> other);
default T get() {
return getOrElseThrow();
}
default T getOrElse(T value) {
return getOrElse(Producer.cons(value));
}
default T getOrElse(Producer extends T> value) {
return await().getOrElse(value);
}
default T getOrElseThrow() {
return await().getOrElseThrow();
}
default T getOrElseThrow(Producer extends X> producer) throws X {
return await().getOrElseThrow(producer);
}
default Throwable getCause() {
return await().getCause();
}
default Future recover(Function1 super Throwable, ? extends T> mapper) {
return fold(mapper, identity());
}
Future recoverWith(Class type, Function1 super X, ? extends T> mapper);
Future fold(
Function1 super Throwable, ? extends U> failureMapper,
Function1 super T, ? extends U> successMapper);
default CompletableFuture toCompletableFuture() {
CompletableFuture completableFuture = new CompletableFuture<>();
onSuccess(completableFuture::complete);
onFailure(completableFuture::completeExceptionally);
return completableFuture;
}
Promise toPromise();
static Future success(T value) {
return success(DEFAULT_EXECUTOR, value);
}
static Future success(Executor executor, T value) {
return FutureImpl.sync(executor, Try.success(value));
}
static Future failure(Throwable error) {
return failure(DEFAULT_EXECUTOR, error);
}
static Future failure(Executor executor, Throwable error) {
return FutureImpl.sync(executor, Try.failure(error));
}
static Future from(Callable extends T> callable) {
return from(DEFAULT_EXECUTOR, callable);
}
static Future from(Executor executor, Callable extends T> callable) {
return task(executor, callable::call);
}
static Future from(java.util.concurrent.Future extends T> future) {
return from(DEFAULT_EXECUTOR, future);
}
static Future from(Executor executor, java.util.concurrent.Future extends T> future) {
return task(executor, future::get);
}
static Future from(CompletableFuture extends T> future) {
return from(DEFAULT_EXECUTOR, future);
}
static Future from(Executor executor, CompletableFuture extends T> future) {
return from(executor, Promise.from(future));
}
static Future from(Promise extends T> promise) {
return from(DEFAULT_EXECUTOR, promise);
}
static Future from(Executor executor, Promise extends T> promise) {
return FutureImpl.from(executor, promise);
}
static Future task(Producer extends T> task) {
return task(DEFAULT_EXECUTOR, task);
}
static Future task(Executor executor, Producer extends T> task) {
return FutureImpl.task(executor, task.liftTry());
}
static Future exec(CheckedRunnable task) {
return exec(DEFAULT_EXECUTOR, task);
}
static Future exec(Executor executor, CheckedRunnable task) {
return task(executor, task.asProducer());
}
static Future delay(Duration timeout, Producer extends T> producer) {
return delay(DEFAULT_EXECUTOR, timeout, producer);
}
static Future delay(Executor executor, Duration timeout, Producer extends T> producer) {
return sleep(executor, timeout).flatMap(ignore -> task(executor, producer));
}
static Future sleep(Duration delay) {
return sleep(DEFAULT_EXECUTOR, delay);
}
static Future sleep(Executor executor, Duration delay) {
return FutureImpl.sleep(executor, delay);
}
static Future later(Producer extends T> producer) {
return later(DEFAULT_EXECUTOR, producer);
}
static Future later(Executor executor, Producer extends T> producer) {
return task(executor, producer);
}
static Future bracket(Future extends T> acquire,
Function1 super T, ? extends Future extends R>> use) {
return bracket(DEFAULT_EXECUTOR, acquire, use);
}
static Future bracket(Executor executor,
Future extends T> acquire,
Function1 super T, ? extends Future extends R>> use) {
return FutureImpl.bracket(executor, acquire, use, AutoCloseable::close);
}
static Future bracket(Future extends T> acquire,
Function1 super T, ? extends Future extends R>> use,
Consumer1 super T> release) {
return bracket(DEFAULT_EXECUTOR, acquire, use, release);
}
static Future bracket(Executor executor,
Future extends T> acquire,
Function1 super T, ? extends Future extends R>> use,
Consumer1 super T> release) {
return FutureImpl.bracket(executor, acquire, use, release);
}
// TODO
static Future> traverse(Sequence> sequence) {
return sequence.foldLeft(success(ImmutableList.empty()),
(Future> xs, Future a) -> map2(xs, a, Sequence::append));
}
static Future map2(Future extends T> fa, Future extends V> fb,
Function2 super T, ? super V, ? extends R> mapper) {
return fb.ap(fa.map(mapper.curried()));
}
static Future> tuple(Future fa, Future fb) {
return map2(fa, fb, Tuple2::of);
}
static Future async(Consumer1>> consumer) {
return async(DEFAULT_EXECUTOR, consumer);
}
static Future async(Executor executor, Consumer1>> consumer) {
return FutureImpl.async(executor, consumer);
}
}
final class FutureImpl implements Future {
private final Executor executor;
private final Propagate propagate;
private final Promise promise;
private final Cancellable cancellable;
private final UUID uuid;
private FutureImpl(Executor executor, Callback callback) {
this(executor, callback, Propagate.noop());
}
private FutureImpl(Executor executor, Callback callback, Propagate propagate) {
this.uuid = UUID.randomUUID();
this.executor = checkNonNull(executor);
this.propagate = checkNonNull(propagate);
this.promise = Promise.make(executor);
this.cancellable = Cancellable.from(promise);
callback.accept(promise, cancellable);
}
@Override
public Future onComplete(Consumer1 super Try extends T>> consumer) {
promise.onComplete(consumer);
return this;
}
@Override
public Future onSuccess(Consumer1 super T> consumer) {
promise.onSuccess(consumer);
return this;
}
@Override
public Future onFailure(Consumer1 super Throwable> consumer) {
promise.onFailure(consumer);
return this;
}
@Override
public boolean isCompleted() {
return promise.isCompleted();
}
@Override
public boolean isCancelled() {
return cancellable.isCancelled();
}
@Override
public Try await() {
return promise.await();
}
@Override
public Try await(Duration timeout) {
return promise.await(timeout);
}
@Override
public Future map(Function1 super T, ? extends R> mapper) {
return transform(value -> value.map(mapper));
}
@Override
public Future mapError(Function1 super Throwable, ? extends Throwable> mapper) {
return transform(value -> value.mapError(mapper));
}
@Override
public Future flatMap(Function1 super T, ? extends Kind, ? extends R>> mapper) {
return chain(value -> value.fold(e -> Future.failure(executor, e), mapper));
}
@Override
public Future andThen(Future extends R> next) {
return flatMap(ignore -> next);
}
@Override
public Future ap(Future> apply) {
checkNonNull(apply);
return new FutureImpl<>(executor,
(p, c) -> promise.onComplete(try1 -> apply.onComplete(
try2 -> p.tryComplete(Try.map2(try2, try1, Function1::apply)))), this::cancel);
}
@Override
public Future filter(Matcher1 super T> matcher) {
return transform(value -> value.filter(matcher));
}
@Override
public Future orElse(Future extends T> other) {
return chain(value -> value.fold(cons(other), t -> Future.success(executor, t)));
}
@Override
public Future recoverWith(Class type, Function1 super X, ? extends T> mapper) {
return transform(value -> {
Try try1 = TryOf.toTry(value);
return try1.recoverWith(type, mapper);
});
}
@Override
public Future fold(
Function1 super Throwable, ? extends U> failureMapper, Function1 super T, ? extends U> successMapper) {
return transform(value -> Try.success(value.fold(failureMapper, successMapper)));
}
@Override
public void cancel(boolean mayInterruptThread) {
try {
cancellable.cancel(mayInterruptThread);
} finally {
propagate.accept(mayInterruptThread);
}
}
@Override
public Promise toPromise() {
return promise;
}
@Override
public String toString() {
return "Future(" + uuid + ')';
}
private Future transform(Function1 super Try extends T>, ? extends Try extends R>> mapper) {
checkNonNull(mapper);
return new FutureImpl<>(executor,
(p, c) ->
promise.onComplete(value -> p.tryComplete(mapper.apply(value))), this::cancel);
}
private Future chain(Function1 super Try extends T>, ? extends Kind, ? extends R>> mapper) {
checkNonNull(executor);
checkNonNull(mapper);
return new FutureImpl<>(executor,
(p, c) ->
promise.onComplete(value -> mapper.andThen(FutureOf::toFuture).apply(value).onComplete(p::tryComplete)), this::cancel);
}
static Future sync(Executor executor, Try extends T> result) {
checkNonNull(executor);
checkNonNull(result);
return new FutureImpl<>(executor, (p, c) -> p.tryComplete(result));
}
static Future task(Executor executor, Producer extends Try extends T>> producer) {
checkNonNull(executor);
checkNonNull(producer);
return new FutureImpl<>(executor,
(p, c) -> executor.execute(() -> {
c.updateThread();
p.tryComplete(producer.get());
}));
}
static Future async(Executor executor,
Consumer1>> consumer) {
checkNonNull(executor);
checkNonNull(consumer);
return new FutureImpl<>(executor,
(p, c) -> Future.later(executor, () -> {
c.updateThread();
return consumer.asFunction().apply(p::tryComplete);
}));
}
static Future from(Executor executor, Promise extends T> promise) {
checkNonNull(executor);
checkNonNull(promise);
return new FutureImpl<>(executor, (p, c) -> promise.onComplete(p::tryComplete));
}
static Future sleep(Executor executor, Duration delay) {
checkNonNull(executor);
checkNonNull(delay);
return from(executor, FutureModule.sleep(executor, delay));
}
static Future bracket(Executor executor, Future extends T> acquire,
Function1 super T, ? extends Future extends R>> use, Consumer1 super T> release) {
checkNonNull(executor);
checkNonNull(acquire);
checkNonNull(use);
checkNonNull(release);
return new FutureImpl<>(executor,
(p, c) -> acquire.onComplete(
resource -> resource.fold(e -> Future.failure(executor, e), use)
.onComplete(p::tryComplete)
.onComplete(result -> resource.onSuccess(release))));
}
}
interface FutureModule {
ScheduledExecutorService SCHEDULER = Executors.newScheduledThreadPool(0);
static Promise sleep(Executor executor, Duration delay) {
return Promise.from(executor, supplyAsync(Unit::unit, delayedExecutor(delay, executor)));
}
static Executor delayedExecutor(Duration delay, Executor executor) {
return task -> SCHEDULER.schedule(() -> executor.execute(task), delay.toMillis(), TimeUnit.MILLISECONDS);
}
}
interface Callback {
void accept(Promise promise, Cancellable cancellable);
}
interface Propagate {
void accept(boolean mayInterruptThread);
static Propagate noop() {
return mayInterruptThread -> {};
}
}