com.github.tonivade.purefun.effect.PureIO Maven / Gradle / Ivy
/*
* Copyright (c) 2018-2022, Antonio Gabriel Muñoz Conejo
* Distributed under the terms of the MIT License
*/
package com.github.tonivade.purefun.effect;
import static com.github.tonivade.purefun.Function1.identity;
import static com.github.tonivade.purefun.Function2.first;
import static com.github.tonivade.purefun.Function2.second;
import static com.github.tonivade.purefun.Matcher1.always;
import static com.github.tonivade.purefun.Precondition.checkNonNull;
import static com.github.tonivade.purefun.Producer.cons;
import java.time.Duration;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import com.github.tonivade.purefun.CheckedRunnable;
import com.github.tonivade.purefun.Consumer1;
import com.github.tonivade.purefun.Consumer2;
import com.github.tonivade.purefun.Effect;
import com.github.tonivade.purefun.Function1;
import com.github.tonivade.purefun.Function2;
import com.github.tonivade.purefun.HigherKind;
import com.github.tonivade.purefun.Kind;
import com.github.tonivade.purefun.Nothing;
import com.github.tonivade.purefun.Operator1;
import com.github.tonivade.purefun.PartialFunction1;
import com.github.tonivade.purefun.Producer;
import com.github.tonivade.purefun.Recoverable;
import com.github.tonivade.purefun.Tuple;
import com.github.tonivade.purefun.Tuple2;
import com.github.tonivade.purefun.Unit;
import com.github.tonivade.purefun.concurrent.Future;
import com.github.tonivade.purefun.concurrent.Promise;
import com.github.tonivade.purefun.data.ImmutableList;
import com.github.tonivade.purefun.data.Sequence;
import com.github.tonivade.purefun.type.Either;
import com.github.tonivade.purefun.type.EitherOf;
import com.github.tonivade.purefun.type.Option;
import com.github.tonivade.purefun.type.Try;
import com.github.tonivade.purefun.typeclasses.Fiber;
@HigherKind
public sealed interface PureIO extends PureIOOf, Effect, E>, A> {
default Either provide(R env) {
return runAsync(env).getOrElseThrow();
}
default Future> runAsync(R env) {
return Future.from(runAsync(env, this, PureIOConnection.UNCANCELLABLE));
}
default Future> runAsync(R env, Executor executor) {
return PureIO.forked(executor).andThen(this).runAsync(env);
}
default void provideAsync(R env, Consumer1 super Try extends Either>> callback) {
runAsync(env).onComplete(callback::accept);
}
default void provideAsync(R env, Executor executor, Consumer1 super Try extends Either>> callback) {
runAsync(env, executor).onComplete(callback::accept);
}
default PureIO swap() {
return foldM(PureIO::pure, PureIO::raiseError);
}
@Override
default PureIO map(Function1 super A, ? extends B> map) {
return flatMap(map.andThen(PureIO::pure));
}
default PureIO mapError(Function1 super E, ? extends B> map) {
return flatMapError(map.andThen(PureIO::raiseError));
}
default PureIO bimap(Function1 super E, ? extends F> mapError, Function1 super A, ? extends B> map) {
return foldM(mapError.andThen(PureIO::raiseError), map.andThen(PureIO::pure));
}
@Override
default PureIO flatMap(Function1 super A, ? extends Kind, E>, ? extends B>> map) {
return foldM(PureIO::raiseError, map.andThen(PureIOOf::narrowK));
}
default PureIO flatMapError(Function1 super E, ? extends Kind, F>, ? extends A>> map) {
return foldM(map, PureIO::pure);
}
default PureIO foldM(
Function1 super E, ? extends Kind, F>, ? extends B>> left,
Function1 super A, ? extends Kind, F>, ? extends B>> right) {
return new FlatMapped<>(this, left.andThen(PureIOOf::narrowK), right.andThen(PureIOOf::narrowK));
}
@Override
default PureIO andThen(Kind, E>, ? extends B> next) {
return flatMap(ignore -> next);
}
@Override
default PureIO ap(Kind, E>, Function1 super A, ? extends B>> apply) {
return parMap2(this, apply.fix(PureIOOf.toPureIO()), (v, a) -> a.apply(v));
}
default URIO fold(
Function1 super E, ? extends B> mapError, Function1 super A, ? extends B> map) {
return new URIO<>(foldM(mapError.andThen(PureIO::pure), map.andThen(PureIO::pure)));
}
default URIO recover(Function1 super E, ? extends A> mapError) {
return fold(mapError, identity());
}
default PureIO orElse(Kind, E>, ? extends A> other) {
return foldM(Function1.cons(other), Function1.cons(this));
}
@Override
default PureIO> zip(Kind, E>, ? extends B> other) {
return zipWith(other, Tuple::of);
}
@Override
default PureIO zipLeft(Kind, E>, ? extends B> other) {
return zipWith(other, first());
}
@Override
default PureIO zipRight(Kind, E>, ? extends B> other) {
return zipWith(other, second());
}
@Override
default PureIO zipWith(Kind, E>, ? extends B> other,
Function2 super A, ? super B, ? extends C> mapper) {
return parMap2(this, other.fix(PureIOOf.toPureIO()), mapper);
}
@Override
default PureIO repeat() {
return repeat(1);
}
@Override
default PureIO repeat(int times) {
return repeat(Schedule.recurs(times).zipRight(Schedule.identity()));
}
@Override
default PureIO repeat(Duration delay) {
return repeat(delay, 1);
}
@Override
default PureIO repeat(Duration delay, int times) {
return repeat(Schedule.recursSpaced(delay, times).zipRight(Schedule.identity()));
}
default PureIO repeat(Schedule schedule) {
return repeatOrElse(schedule, (e, b) -> raiseError(e));
}
default PureIO repeatOrElse(
Schedule schedule,
Function2, Kind, E>, B>> orElse) {
return repeatOrElseEither(schedule, orElse).map(Either::merge);
}
default PureIO> repeatOrElseEither(
Schedule schedule,
Function2, Kind, E>, C>> orElse) {
return new Repeat<>(this, schedule, orElse.andThen(PureIOOf::narrowK)).run();
}
@Override
default PureIO retry() {
return retry(1);
}
@Override
default PureIO retry(int maxRetries) {
return retry(Schedule.recurs(maxRetries));
}
@Override
default PureIO retry(Duration delay) {
return retry(delay, 1);
}
@Override
default PureIO retry(Duration delay, int maxRetries) {
return retry(Schedule.recursSpaced(delay, maxRetries));
}
default PureIO retry(Schedule schedule) {
return retryOrElse(schedule, (e, b) -> raiseError(e));
}
default PureIO retryOrElse(
Schedule schedule,
Function2, E>, A>> orElse) {
return retryOrElseEither(schedule, orElse).map(Either::merge);
}
default PureIO> retryOrElseEither(
Schedule schedule,
Function2, E>, B>> orElse) {
return new Retry<>(this, schedule, orElse.andThen(PureIOOf::narrowK)).run();
}
@Override
default PureIO> timed() {
return PureIO.later(System::nanoTime).flatMap(
start -> map(result -> Tuple.of(Duration.ofNanos(System.nanoTime() - start), result)));
}
default PureIO, E>, A>> fork() {
return async((env, callback) -> {
PureIOConnection connection = PureIOConnection.cancellable();
Promise> promise = runAsync(env, this, connection);
PureIO join = fromPromise(promise);
PureIO cancel = run(connection::cancel);
callback.accept(Try.success(Either.right(Fiber.of(join, cancel))));
});
}
default PureIO timeout(Duration duration) {
return timeout(Future.DEFAULT_EXECUTOR, duration);
}
default PureIO timeout(Executor executor, Duration duration) {
return racePair(executor, this, sleep(duration)).flatMap(either -> either.fold(
ta -> ta.get2().cancel().fix(PureIOOf.toPureIO()).map(x -> ta.get1()),
tb -> tb.get1().cancel().fix(PureIOOf.toPureIO()).flatMap(x -> PureIO.throwError(new TimeoutException()))));
}
@SuppressWarnings("unchecked")
default PureIO refineOrDie(Class type) {
return flatMapError(error -> {
if (type.isAssignableFrom(error.getClass())) {
return PureIO.raiseError((X) error);
}
return PureIO.throwError(new ClassCastException(error.getClass() + " not asignable to " + type));
});
}
default URIO toURIO() {
return new URIO<>(mapError(error -> {
if (error instanceof Throwable throwable) {
throw throwable;
}
throw new ClassCastException(error.getClass() + " is not throwable");
}));
}
default RIO toRIO() {
return new RIO<>(refineOrDie(Throwable.class));
}
default Managed toManaged() {
return Managed.pure(this);
}
default Managed toManaged(Consumer1 super A> release) {
return Managed.from(this, release);
}
static PureIO accessM(Function1 super R, ? extends Kind, E>, ? extends A>> map) {
return new AccessM<>(map.andThen(PureIOOf::narrowK));
}
static PureIO access(Function1 super R, ? extends A> map) {
return accessM(map.andThen(PureIO::pure));
}
static PureIO env() {
return access(identity());
}
static PureIO forked(Executor executor) {
return async((env, callback) -> executor.execute(() -> callback.accept(Try.success(Either.right(Unit.unit())))));
}
static PureIO parMap2(Kind, E>, ? extends A> za, Kind, E>, ? extends B> zb,
Function2 super A, ? super B, ? extends C> mapper) {
return parMap2(Future.DEFAULT_EXECUTOR, za, zb, mapper);
}
static PureIO parMap2(Executor executor, Kind, E>, ? extends A> za, Kind, E>, ? extends B> zb,
Function2 super A, ? super B, ? extends C> mapper) {
return cancellable((env, callback) -> {
PureIOConnection connection1 = PureIOConnection.cancellable();
PureIOConnection connection2 = PureIOConnection.cancellable();
Promise> promiseA = runAsync(env, PureIO.forked(executor).andThen(za), connection1);
Promise> promiseB = runAsync(env, PureIO.forked(executor).andThen(zb), connection2);
promiseA.onComplete(a -> promiseB.onComplete(
b -> callback.accept(Try.map2(a, b, (e1, e2) -> EitherOf.narrowK(Either.map2(e1, e2, mapper))))));
return PureIO.exec(() -> {
try {
connection1.cancel();
} finally {
connection2.cancel();
}
});
});
}
static PureIO> race(Kind, E>, ? extends A> fa, Kind, E>, ? extends B> fb) {
return race(Future.DEFAULT_EXECUTOR, fa, fb);
}
static PureIO> race(Executor executor, Kind, E>, ? extends A> fa, Kind, E>, ? extends B> fb) {
return racePair(executor, fa, fb).flatMap(either -> either.fold(
ta -> ta.get2().cancel().fix(PureIOOf.toPureIO()).map(x -> Either.left(ta.get1())),
tb -> tb.get1().cancel().fix(PureIOOf.toPureIO()).map(x -> Either.right(tb.get2()))));
}
static PureIO, E>, B>>, Tuple2, E>, A>, B>>>
racePair(Executor executor, Kind, E>, ? extends A> fa, Kind, E>, ? extends B> fb) {
return cancellable((env, callback) -> {
PureIOConnection connection1 = PureIOConnection.cancellable();
PureIOConnection connection2 = PureIOConnection.cancellable();
Promise> promiseA = runAsync(env, PureIO.forked(executor).andThen(fa), connection1);
Promise> promiseB = runAsync(env, PureIO.forked(executor).andThen(fb), connection2);
promiseA.onComplete(result -> {
PureIO fromPromiseB = PureIO.fromPromise(promiseB);
PureIO cancelB = PureIO.run(connection2::cancel);
Fiber, E>, B> fiberB = Fiber.of(fromPromiseB, cancelB);
callback.accept(result.map(
either -> either.map(
a -> Either., E>, B>>, Tuple2, E>, A>, B>>left(Tuple.of(a, fiberB)))));
});
promiseB.onComplete(result -> {
PureIO fromPromiseA = PureIO.fromPromise(promiseA);
PureIO cancelA = PureIO.run(connection2::cancel);
Fiber, E>, A> fiberA = Fiber.of(fromPromiseA, cancelA);
callback.accept(result.map(
either -> either.map(
b -> Either., E>, B>>, Tuple2, E>, A>, B>>right(Tuple.of(fiberA, b)))));
});
return PureIO.exec(() -> {
try {
connection1.cancel();
} finally {
connection2.cancel();
}
});
});
}
static PureIO absorb(Kind, E>, Either> value) {
return value.fix(PureIOOf::narrowK).flatMap(either -> either.fold(PureIO::raiseError, PureIO::pure));
}
static Function1> lift(Function1 super A, ? extends B> function) {
return value -> pure(function.apply(value));
}
static Function1> liftOption(Function1 super A, ? extends Option extends B>> function) {
return value -> fromOption(function.apply(value));
}
static Function1> liftTry(Function1 super A, ? extends Try extends B>> function) {
return value -> fromTry(function.apply(value));
}
static Function1> liftEither(Function1 super A, ? extends Either> function) {
return value -> fromEither(function.apply(value));
}
static PureIO fromOption(Option extends A> task) {
return fromOption(cons(task));
}
static PureIO fromOption(Producer