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

com.github.tonivade.purefun.effect.UIO Maven / Gradle / Ivy

/*
 * Copyright (c) 2018-2021, 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.Nothing.nothing;
import static com.github.tonivade.purefun.Precondition.checkNonNull;

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.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.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 UIO implements UIOOf, Effect, Recoverable {

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

  private final PureIO instance;

  UIO(PureIO value) {
    this.instance = checkNonNull(value);
  }
  
  public Future runAsync() {
    return instance.runAsync(nothing()).map(Either::getRight);
  }

  public Future runAsync(Executor executor) {
    return UIO.forked(executor).andThen(this).runAsync();
  }

  public A unsafeRunSync() {
    return instance.provide(nothing()).get();
  }

  public Try safeRunSync() {
    return Try.of(this::unsafeRunSync);
  }

  @SuppressWarnings("unchecked")
  public  PureIO toPureIO() {
    return (PureIO) instance;
  }

  @SuppressWarnings("unchecked")
  public  EIO toEIO() {
    return new EIO<>((PureIO) instance);
  }

  @SuppressWarnings("unchecked")
  public  RIO toRIO() {
    return new RIO<>((PureIO) PureIO.redeem(instance));
  }

  @SuppressWarnings("unchecked")
  public  URIO toURIO() {
    return new URIO<>((PureIO) instance);
  }

  public Task toTask() {
    return new Task<>(PureIO.redeem(instance));
  }

  public void safeRunAsync(Consumer1> callback) {
    instance.provideAsync(nothing(), x -> callback.accept(x.map(Either::getRight)));
  }

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

  @Override
  public  UIO flatMap(Function1> map) {
    return new UIO<>(instance.flatMap(x -> {
      UIO apply = map.andThen(UIOOf::narrowK).apply(x);
      return apply.instance;
    }));
  }

  @Override
  public  UIO andThen(Kind next) {
    return new UIO<>(instance.andThen(next.fix(UIOOf.toUIO()).instance));
  }

  @Override
  public  UIO ap(Kind> apply) {
    return new UIO<>(instance.ap(apply.fix(UIOOf.toUIO()).instance));
  }

  public UIO recover(Function1 mapError) {
    return redeem(mapError, identity());
  }

  @SuppressWarnings("unchecked")
  public  UIO recoverWith(Class type, Function1 function) {
    return recover(cause -> {
      if (type.isAssignableFrom(cause.getClass())) {
        return function.apply((X) cause);
      }
      return sneakyThrow(cause);
    });
  }

  public  UIO redeem(
      Function1 mapError, Function1 map) {
    return redeemWith(mapError.andThen(UIO::pure), map.andThen(UIO::pure));
  }

  public  UIO redeemWith(
      Function1> mapError, 
      Function1> map) {
    return new UIO<>(PureIO.redeem(instance).foldM(
        error -> mapError.andThen(UIOOf::narrowK).apply(error).instance, 
        value -> map.andThen(UIOOf::narrowK).apply(value).instance));
  }
  
  @Override
  public  UIO> zip(Kind other) {
    return zipWith(other, Tuple::of);
  }
  
  @Override
  public  UIO zipLeft(Kind other) {
    return zipWith(other, first());
  }
  
  @Override
  public  UIO zipRight(Kind other) {
    return zipWith(other, second());
  }
  
  @Override
  public  UIO zipWith(Kind other, 
      Function2 mapper) {
    return parMap2(this, other.fix(UIOOf.toUIO()), mapper);
  }
  
  public UIO> fork() {
    return new UIO<>(instance.fork().map(f -> f.mapK(new FunctionK, Nothing>, UIO_>() {
      @Override
      public  UIO apply(Kind, Nothing>, ? extends T> from) {
        return new UIO<>(from.fix(PureIOOf::narrowK));
      }
    })));
  }

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

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

  @Override
  public UIO repeat(int times) {
    return fold(PureIO.redeem(instance).repeat(times));
  }

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

  @Override
  public UIO repeat(Duration delay, int times) {
    return fold(PureIO.redeem(instance).repeat(delay, times));
  }
  
  public  UIO repeat(Schedule schedule) {
    return fold(PureIO.redeem(instance).repeat(schedule));
  }

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

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

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

  @Override
  public UIO retry(Duration delay, int maxRetries) {
    return retry(Schedule.recursSpaced(delay, maxRetries));
  }
  
  public  UIO retry(Schedule schedule) {
    return fold(PureIO.redeem(instance).retry(schedule));
  }

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

  public static  UIO parMap2(Kind za, Kind zb, 
      Function2 mapper) {
    return parMap2(Future.DEFAULT_EXECUTOR, za, zb, mapper);
  }

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

  public static  Function1> lift(Function1 function) {
    return value -> task(() -> function.apply(value));
  }

  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 UIO sleep(Duration delay) {
    return fold(PureIO.sleep(delay));
  }

  public static UIO exec(CheckedRunnable task) {
    return fold(PureIO.exec(task));
  }

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

  public static  UIO raiseError(Throwable throwable) {
    return new UIO<>(PureIO.fromEither(() -> { throw throwable; }));
  }

  public static  UIO defer(Producer> lazy) {
    return new UIO<>(PureIO.defer(() -> lazy.andThen(UIOOf::narrowK).get().instance));
  }

  public static  UIO task(Producer task) {
    return fold(PureIO.task(task));
  }

  public static  UIO fromOption(Option task) {
    return fromEither(task.toEither());
  }

  public static  UIO fromTry(Try task) {
    return fromEither(task.toEither());
  }

  public static  UIO fromEither(Either task) {
    return task.fold(UIO::raiseError, UIO::pure);
  }
  
  public static  UIO never() {
    return async(cb -> {});
  }
  
  public static  UIO async(Consumer1>> consumer) {
    return fold(PureIO.async(
        (env, cb1) -> consumer.accept(result -> cb1.accept(result.map(Either::right)))));
  }
  
  public static  UIO cancellable(Function1>, UIO> consumer) {
    return fold(PureIO.cancellable(
        (env, cb1) -> consumer.andThen(UIO::toPureIO).apply(result -> cb1.accept(result.map(Either::right)))));
  }

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

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

  public static  UIO bracket(
      Kind acquire, Function1> use) {
    return fold(PureIO.bracket(PureIO.redeem(acquire.fix(UIOOf::narrowK).instance), 
        resource -> PureIO.redeem(use.andThen(UIOOf::narrowK).apply(resource).instance)));
  }

  public static  UIO bracket(Kind acquire, 
      Function1> use, Consumer1 release) {
    return fold(PureIO.bracket(PureIO.redeem(acquire.fix(UIOOf::narrowK).instance), 
        resource -> PureIO.redeem(use.andThen(UIOOf::narrowK).apply(resource).instance), release));
  }

  public static  UIO bracket(Kind acquire, 
      Function1> use, Function1> release) {
    return fold(PureIO.bracket(PureIO.redeem(acquire.fix(UIOOf::narrowK).instance), 
        resource -> PureIO.redeem(use.andThen(UIOOf::narrowK).apply(resource).instance), release.andThen(UIOOf::narrowK).andThen(UIO::toPureIO)));
  }

  public static UIO unit() {
    return UNIT;
  }

  private static  UIO fold(PureIO zio) {
    return new UIO<>(zio.foldM(error -> UIO.raiseError(error).instance, value -> UIO.pure(value).instance));
  }
}