All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.tonivade.purefun.effect.EIO 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.Function2.first;
import static com.github.tonivade.purefun.Function2.second;
import static com.github.tonivade.purefun.Nothing.nothing;
import static com.github.tonivade.purefun.Precondition.checkNonNull;
import static com.github.tonivade.purefun.Producer.cons;

import java.time.Duration;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeoutException;

import com.github.tonivade.purefun.CheckedRunnable;
import com.github.tonivade.purefun.Consumer1;
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.Producer;
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.data.ImmutableList;
import com.github.tonivade.purefun.data.Sequence;
import com.github.tonivade.purefun.type.Either;
import com.github.tonivade.purefun.type.Option;
import com.github.tonivade.purefun.type.Try;
import com.github.tonivade.purefun.typeclasses.Fiber;
import com.github.tonivade.purefun.typeclasses.FunctionK;

@HigherKind
public final class EIO implements EIOOf, Effect, A> {

  private static final EIO UNIT = new EIO<>(PureIO.unit());

  private final PureIO instance;

  EIO(PureIO value) {
    this.instance = checkNonNull(value);
  }

  @SuppressWarnings("unchecked")
  public  PureIO toPureIO() {
    return (PureIO) instance;
  }
  
  public UIO toUIO() {
    return new UIO<>(instance.toURIO().toPureIO());
  }

  public Either safeRunSync() {
    return instance.provide(nothing());
  }

  public Future> runAsync() {
    return instance.runAsync(nothing());
  }

  public Future> runAsync(Executor executor) {
    return EIO.forked(executor).andThen(this).runAsync();
  }
  
  public void safeRunAsync(Consumer1>> callback) {
    instance.provideAsync(nothing(), callback);
  }

  @Override
  public  EIO map(Function1 map) {
    return new EIO<>(instance.map(map));
  }

  @Override
  public  EIO flatMap(Function1, ? extends B>> map) {
    return new EIO<>(instance.flatMap(value -> {
      EIO apply = map.andThen(EIOOf::narrowK).apply(value);
      return apply.instance;
    }));
  }

  @Override
  public  EIO andThen(Kind, ? extends B> next) {
    return new EIO<>(instance.andThen(next.fix(EIOOf.toEIO()).instance));
  }

  @Override
  public  EIO ap(Kind, Function1> apply) {
    return new EIO<>(instance.ap(apply.fix(EIOOf.toEIO()).toPureIO()));
  }

  public EIO swap() {
    return new EIO<>(instance.swap());
  }

  public  EIO mapError(Function1 map) {
    return new EIO<>(instance.mapError(map));
  }

  public  EIO flatMapError(Function1, ? extends A>> map) {
    return new EIO<>(instance.flatMapError(error -> {
      EIO apply = map.andThen(EIOOf::narrowK).apply(error);
      return apply.instance;
    }));
  }

  public  EIO bimap(Function1 mapError, Function1 map) {
    return new EIO<>(instance.bimap(mapError, map));
  }

  public  EIO foldM(
      Function1, ? extends B>> mapError, 
      Function1, ? extends B>> map) {
    return new EIO<>(instance.foldM(
        error -> mapError.andThen(EIOOf::narrowK).apply(error).instance, 
        value -> map.andThen(EIOOf::narrowK).apply(value).instance));
  }

  public  UIO fold(Function1 mapError, Function1 map) {
    return new UIO<>(instance.fold(mapError, map).toPureIO());
  }

  public UIO recover(Function1 mapError) {
    return new UIO<>(instance.recover(mapError).toPureIO());
  }

  public EIO orElse(EIO other) {
    return new EIO<>(instance.orElse(other.instance));
  }
  
  @Override
  public  EIO> zip(Kind, ? extends B> other) {
    return zipWith(other, Tuple::of);
  }
  
  @Override
  public  EIO zipLeft(Kind, ? extends B> other) {
    return zipWith(other, first());
  }
  
  @Override
  public  EIO zipRight(Kind, ? extends B> other) {
    return zipWith(other, second());
  }
  
  @Override
  public  EIO zipWith(Kind, ? extends B> other, 
      Function2 mapper) {
    return parMap2(this, other.fix(EIOOf.toEIO()), mapper);
  }
  
  public EIO, A>> fork() {
    return new EIO<>(instance.fork().map(f -> f.mapK(new FunctionK, E>, Kind>() {
      @Override
      public  EIO apply(Kind, E>, ? extends T> from) {
        return new EIO<>(from.fix(PureIOOf::narrowK));
      }
    })));
  }

  public EIO timeout(Duration duration) {
    return timeout(Future.DEFAULT_EXECUTOR, duration);
  }
  
  public EIO timeout(Executor executor, Duration duration) {
    return racePair(executor, this, EIO.sleep(duration)).flatMap(either -> either.fold(
        ta -> ta.get2().cancel().fix(EIOOf.toEIO()).map(x -> ta.get1()),
        tb -> tb.get1().cancel().fix(EIOOf.toEIO()).flatMap(x -> EIO.throwError(new TimeoutException()))));
  }

  @Override
  public EIO repeat() {
    return repeat(1);
  }

  @Override
  public EIO repeat(int times) {
    return new EIO<>(instance.repeat(times));
  }

  @Override
  public EIO repeat(Duration delay) {
    return repeat(delay, 1);
  }

  @Override
  public EIO repeat(Duration delay, int times) {
    return new EIO<>(instance.repeat(delay, times));
  }
  
  public  EIO repeat(Schedule schedule) {
    return new EIO<>(instance.repeat(schedule));
  }

  @Override
  public EIO retry() {
    return retry(1);
  }

  @Override
  public EIO retry(int maxRetries) {
    return retry(Schedule.recurs(maxRetries));
  }

  @Override
  public EIO retry(Duration delay) {
    return retry(delay, 1);
  }

  @Override
  public EIO retry(Duration delay, int maxRetries) {
    return retry(Schedule.recursSpaced(delay, maxRetries));
  }
  
  public  EIO retry(Schedule schedule) {
    return new EIO<>(instance.retry(schedule));
  }

  @Override
  public EIO> timed() {
    return new EIO<>(instance.timed());
  }
  
  public static  EIO forked(Executor executor) {
    return async(callback -> executor.execute(() -> callback.accept(Try.success(Either.right(Unit.unit())))));
  }
  
  public  EIO refineOrDie(Class type) {
    return new EIO<>(instance.refineOrDie(type));
  }

  public static  EIO parMap2(Kind, ? extends A> za, Kind, ? extends B> zb, 
      Function2 mapper) {
    return parMap2(Future.DEFAULT_EXECUTOR, za, zb, mapper);
  }

  public static  EIO parMap2(Executor executor, Kind, ? extends A> za, Kind, ? extends B> zb, 
      Function2 mapper) {
    return new EIO<>(PureIO.parMap2(executor, za.fix(EIOOf::narrowK).instance, zb.fix(EIOOf::narrowK).instance, mapper));
  }
  
  public static  EIO> race(Kind, ? extends A> fa, Kind, ? extends B> fb) {
    return race(Future.DEFAULT_EXECUTOR, fa, fb);
  }
  
  public static  EIO> race(Executor executor, Kind, ? extends A> fa, Kind, ? extends B> fb) {
    return racePair(executor, fa, fb).flatMap(either -> either.fold(
        ta -> ta.get2().cancel().fix(EIOOf.toEIO()).map(x -> Either.left(ta.get1())),
        tb -> tb.get1().cancel().fix(EIOOf.toEIO()).map(x -> Either.right(tb.get2()))));
  }
  
  public static  EIO, B>>, Tuple2, A>, B>>> 
      racePair(Executor executor, Kind, ? extends A> fa, Kind, ? extends B> fb) {
    PureIO instance1 = fa.fix(EIOOf.toEIO()).instance.fix(PureIOOf::narrowK);
    PureIO instance2 = fb.fix(EIOOf.toEIO()).instance.fix(PureIOOf::narrowK);
    return new EIO<>(PureIO.racePair(executor, instance1, instance2).map(
      either -> either.bimap(a -> a.map2(f -> f.mapK(new FunctionK, E>, Kind>() {
        @Override
        public  EIO apply(Kind, E>, ? extends T> from) {
          return new EIO<>(from.fix(PureIOOf::narrowK));
        }
      })), b -> b.map1(f -> f.mapK(new FunctionK, E>, Kind>() {
        @Override
        public  EIO apply(Kind, E>, ? extends T> from) {
          return new EIO<>(from.fix(PureIOOf::narrowK));
        }
      })))));
  }

  public static  EIO absorb(EIO> value) {
    return new EIO<>(PureIO.absorb(value.instance));
  }

  public static  Function1> lift(Function1 function) {
    return PureIO.lift(function).andThen(EIO::new);
  }

  public static  Function1> liftOption(Function1> function) {
    return value -> fromOption(function.apply(value));
  }

  public static  Function1> liftTry(Function1> function) {
    return value -> fromTry(function.apply(value));
  }

  public static  Function1> liftEither(Function1> function) {
    return value -> fromEither(function.apply(value));
  }

  public static  EIO fromOption(Option task) {
    return fromOption(cons(task));
  }

  public static  EIO fromOption(Producer> task) {
    return new EIO<>(PureIO.fromOption(task));
  }

  public static  EIO fromTry(Try task) {
    return fromTry(cons(task));
  }

  public static  EIO fromTry(Producer> task) {
    return new EIO<>(PureIO.fromTry(task));
  }

  public static  EIO fromEither(Either task) {
    return fromEither(cons(task));
  }

  public static  EIO fromEither(Producer> task) {
    return new EIO<>(PureIO.fromEither(task));
  }

  public static  EIO sleep(Duration delay) {
    return sleep(Future.DEFAULT_EXECUTOR, delay);
  }

  public static  EIO sleep(Executor executor, Duration delay) {
    return new EIO<>(PureIO.sleep(executor, delay));
  }

  public static EIO exec(CheckedRunnable task) {
    return new EIO<>(PureIO.exec(task));
  }

  public static  EIO pure(A value) {
    return new EIO<>(PureIO.pure(value));
  }

  public static  EIO defer(Producer, ? extends A>> lazy) {
    return new EIO<>(PureIO.defer(() -> lazy.andThen(EIOOf::narrowK).get().instance));
  }

  public static  EIO task(Producer task) {
    return new EIO<>(PureIO.task(task));
  }
  
  public static  EIO never() {
    return async(cb -> {});
  }
  
  public static  EIO async(Consumer1>>> consumer) {
    return new EIO<>(PureIO.async((env, cb) -> consumer.accept(cb)));
  }
  
  public static  EIO cancellable(Function1>>, EIO> consumer) {
    return new EIO<>(PureIO.cancellable((env, cb) -> consumer.andThen(EIO::toPureIO).apply(cb)));
  }

  public static  EIO raiseError(E error) {
    return new EIO<>(PureIO.raiseError(error));
  }

  public static  EIO throwError(Throwable error) {
    return new EIO<>(PureIO.throwError(error));
  }

  public static  EIO> traverse(Sequence> sequence) {
    return traverse(Future.DEFAULT_EXECUTOR, sequence);
  }

  public static  EIO> traverse(Executor executor, Sequence> sequence) {
    return sequence.foldLeft(pure(ImmutableList.empty()), 
        (EIO> xs, EIO a) -> parMap2(executor, xs, a, Sequence::append));
  }

  public static  EIO bracket(Kind, ? extends A> acquire, 
      Function1> use) {
    return new EIO<>(PureIO.bracket(acquire.fix(EIOOf::narrowK).instance, 
        resource -> use.andThen(EIOOf::narrowK).apply(resource).instance));
  }

  public static  EIO bracket(Kind, ? extends A> acquire, 
      Function1, ? extends B>> use, Consumer1 release) {
    return new EIO<>(PureIO.bracket(acquire.fix(EIOOf::narrowK).instance, 
        resource -> use.andThen(EIOOf::narrowK).apply(resource).instance, release));
  }

  public static  EIO bracket(Kind, ? extends A> acquire, 
      Function1, ? extends B>> use, Function1, Unit>> release) {
    return new EIO<>(PureIO.bracket(acquire.fix(EIOOf::narrowK).instance, 
        resource -> use.andThen(EIOOf::narrowK).apply(resource).instance, release.andThen(EIOOf::narrowK).andThen(EIO::toPureIO)));
  }

  @SuppressWarnings("unchecked")
  public static  EIO unit() {
    return (EIO) UNIT;
  }
}