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

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

/*
 * Copyright (c) 2018-2019, 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.Producer.cons;
import static java.util.Objects.requireNonNull;

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

import com.github.tonivade.purefun.CheckedRunnable;
import com.github.tonivade.purefun.Consumer1;
import com.github.tonivade.purefun.Function1;
import com.github.tonivade.purefun.Function2;
import com.github.tonivade.purefun.Higher1;
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.Sealed;
import com.github.tonivade.purefun.Unit;
import com.github.tonivade.purefun.concurrent.Future;
import com.github.tonivade.purefun.type.Either;
import com.github.tonivade.purefun.type.Try;
import com.github.tonivade.purefun.typeclasses.MonadDefer;

@Sealed
@HigherKind
public interface ZIO {

  Either provide(R env);

  default Future> toFuture(R env) {
    return toFuture(Future.DEFAULT_EXECUTOR, env);
  }

  default Future> toFuture(Executor executor, R env) {
    return Future.async(executor, () -> provide(env));
  }

  default void provideAsync(R env, Consumer1>> callback) {
    provideAsync(Future.DEFAULT_EXECUTOR, env, callback);
  }

  default void provideAsync(Executor executor, R env, Consumer1>> callback) {
    toFuture(executor, env).onComplete(callback);
  }

   Higher1> foldMap(R env, MonadDefer monad);

  default ZIO swap() {
    return new Swap<>(this);
  }

  default  ZIO map(Function1 map) {
    return flatMap(map.andThen(ZIO::pure));
  }

  default  ZIO mapError(Function1 map) {
    return flatMapError(map.andThen(ZIO::raiseError));
  }

  default  ZIO bimap(Function1 mapError, Function1 map) {
    return biflatMap(mapError.andThen(ZIO::raiseError), map.andThen(ZIO::pure));
  }

  default  ZIO flatMap(Function1> map) {
    return biflatMap(ZIO::raiseError, map);
  }

  default  ZIO flatMapError(Function1> map) {
    return biflatMap(map, ZIO::pure);
  }

  default  ZIO biflatMap(Function1> left, Function1> right) {
    return new FlatMapped<>(cons(this), left, right);
  }

  default  ZIO andThen(ZIO next) {
    return flatMap(ignore -> next);
  }

  default  ZIO foldM(Function1> mapError, Function1> map) {
    return new FoldM<>(this, mapError, map);
  }

  default  ZIO fold(Function1 mapError, Function1 map) {
    return foldM(mapError.andThen(ZIO::pure), map.andThen(ZIO::pure));
  }

  default ZIO recover(Function1 mapError) {
    return fold(mapError, identity());
  }

  default ZIO orElse(Producer> other) {
    return foldM(other.asFunction(), Function1.cons(this));
  }

  default ZIO repeat() {
    return repeat(1);
  }

  default ZIO repeat(int times) {
    return ZIOModule.repeat(this, UIO.unit(), times);
  }

  default ZIO repeat(Duration delay) {
    return repeat(delay, 1);
  }

  default ZIO repeat(Duration delay, int times) {
    return ZIOModule.repeat(this, UIO.sleep(delay), times);
  }

  default ZIO retry() {
    return retry(1);
  }

  default ZIO retry(int maxRetries) {
    return ZIOModule.retry(this, UIO.unit(), maxRetries);
  }

  default ZIO retry(Duration delay) {
    return retry(delay, 1);
  }

  default ZIO retry(Duration delay, int maxRetries) {
    return ZIOModule.retry(this, UIO.sleep(delay), maxRetries);
  }

  ZIOModule getModule();

  static  ZIO accessM(Function1> map) {
    return new AccessM<>(map);
  }

  static  ZIO access(Function1 map) {
    return accessM(map.andThen(ZIO::pure));
  }

  static  ZIO env() {
    return access(identity());
  }

  static  ZIO map2(ZIO za, ZIO zb, Function2 mapper) {
    return za.flatMap(a -> zb.map(b -> mapper.curried().apply(a).apply(b)));
  }

  static  ZIO absorb(ZIO> value) {
    return value.flatMap(either -> either.fold(ZIO::raiseError, ZIO::pure));
  }

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

  static  ZIO fromEither(Producer> task) {
    return new Task<>(task);
  }

  static  ZIO exec(CheckedRunnable task) {
    return new Attempt<>(task.asProducer());
  }

  static  ZIO pure(A value) {
    return new Pure<>(value);
  }

  static  ZIO defer(Producer> lazy) {
    return new Suspend<>(lazy);
  }

  static  ZIO task(Producer task) {
    return new Attempt<>(task);
  }

  static  ZIO raiseError(E error) {
    return new Failure<>(error);
  }

  static  ZIO redeem(ZIO value) {
    return new Redeem<>(value);
  }

  static  ZIO bracket(ZIO acquire,
                                                                      Function1> use) {
    return new Bracket<>(acquire, use, AutoCloseable::close);
  }

  static  ZIO bracket(ZIO acquire,
                                                Function1> use,
                                                Consumer1 release) {
    return new Bracket<>(acquire, use, release);
  }

  @SuppressWarnings("unchecked")
  static  ZIO unit() {
    return (ZIO) ZIOModule.UNIT;
  }

  final class Pure implements ZIO {

    private final A value;

    protected Pure(A value) {
      this.value = requireNonNull(value);
    }

    @Override
    public Either provide(R env) {
      return Either.right(value);
    }

    @Override
    public  Higher1> foldMap(R env, MonadDefer monad) {
      return monad.pure(Either.right(value));
    }

    @Override
    public ZIOModule getModule() {
      throw new UnsupportedOperationException();
    }

    @Override
    public String toString() {
      return "Pure(" + value + ")";
    }
  }

  final class Failure implements ZIO {

    private E error;

    protected Failure(E error) {
      this.error = requireNonNull(error);
    }

    @Override
    public Either provide(R env) {
      return Either.left(error);
    }

    @Override
    public  Higher1> foldMap(R env, MonadDefer monad) {
      return monad.pure(Either.left(error));
    }

    @Override
    public ZIOModule getModule() {
      throw new UnsupportedOperationException();
    }

    @Override
    public String toString() {
      return "Failure(" + error + ")";
    }
  }

  final class FlatMapped implements ZIO {

    private final Producer> current;
    private final Function1> nextError;
    private final Function1> next;

    protected FlatMapped(Producer> current,
                         Function1> nextError,
                         Function1> next) {
      this.current = requireNonNull(current);
      this.nextError = requireNonNull(nextError);
      this.next = requireNonNull(next);
    }

    @Override
    public Either provide(R env) {
      return ZIOModule.evaluate(env, this);
    }

    @Override
    @SuppressWarnings("unchecked")
    public  ZIO biflatMap(Function1> left, Function1> right) {
      return new FlatMapped<>(
          () -> (ZIO) start(),
          f -> new FlatMapped<>(
              () -> run((Either) Either.left(f)), left::apply, right::apply),
          b -> new FlatMapped<>(
              () -> run((Either) Either.right(b)), left::apply, right::apply)
      );
    }

    @Override
    public  Higher1> foldMap(R env, MonadDefer monad) {
      Higher1> foldMap = current.get().foldMap(env, monad);
      Higher1> map =
          monad.map(foldMap, either -> either.bimap(nextError, next).fold(identity(), identity()));
      return monad.flatMap(map, zio -> zio.foldMap(env, monad));
    }

    @Override
    public ZIOModule getModule() {
      throw new UnsupportedOperationException();
    }

    @Override
    public String toString() {
      return "FlatMapped(" + current + ", ?, ?)";
    }

    protected ZIO start() {
      return current.get();
    }

    protected ZIO run(Either value) {
      return value.bimap(nextError, next).fold(identity(), identity());
    }
  }

  final class Task implements ZIO {

    private final Producer> task;

    protected Task(Producer> task) {
      this.task = requireNonNull(task);
    }

    @Override
    public Either provide(R env) {
      return task.get();
    }

    @Override
    public  Higher1> foldMap(R env, MonadDefer monad) {
      return monad.later(task::get);
    }

    @Override
    public ZIOModule getModule() {
      throw new UnsupportedOperationException();
    }

    @Override
    public String toString() {
      return "Task(?)";
    }
  }

  final class Suspend implements ZIO {

    private final Producer> lazy;

    protected Suspend(Producer> lazy) {
      this.lazy = requireNonNull(lazy);
    }

    @Override
    public Either provide(R env) {
      return ZIOModule.collapse(this).provide(env);
    }

    @Override
    public  ZIO flatMap(Function1> map) {
      return new FlatMapped<>(lazy::get, ZIO::raiseError, map::apply);
    }

    @Override
    public  Higher1> foldMap(R env, MonadDefer monad) {
      return monad.defer(() -> lazy.get().foldMap(env, monad));
    }

    @Override
    public ZIOModule getModule() {
      throw new UnsupportedOperationException();
    }

    protected ZIO next() {
      return lazy.get();
    }

    @Override
    public String toString() {
      return "Suspend(?)";
    }
  }

  final class Swap implements ZIO {

    private final ZIO current;

    protected Swap(ZIO current) {
      this.current = requireNonNull(current);
    }

    @Override
    public Either provide(R env) {
      return current.provide(env).swap();
    }

    @Override
    public  Higher1> foldMap(R env, MonadDefer monad) {
      return monad.map(current.foldMap(env, monad), Either::swap);
    }

    @Override
    public ZIOModule getModule() {
      throw new UnsupportedOperationException();
    }

    @Override
    public String toString() {
      return "Swap(" + current + ")";
    }
  }

  final class Attempt implements ZIO {

    private final Producer current;

    protected Attempt(Producer current) {
      this.current = requireNonNull(current);
    }

    @Override
    public Either provide(R env) {
      return Try.of(current).toEither();
    }

    @Override
    public  Higher1> foldMap(R env, MonadDefer monad) {
      return monad.later(() -> Try.of(current).toEither());
    }

    @Override
    public ZIOModule getModule() {
      throw new UnsupportedOperationException();
    }

    @Override
    public String toString() {
      return "Attempt(" + current + ")";
    }
  }

  final class Redeem implements ZIO {

    private final ZIO current;

    protected Redeem(ZIO current) {
      this.current = requireNonNull(current);
    }

    @Override
    public Either provide(R env) {
      return Try.of(() -> current.provide(env).get()).toEither();
    }

    @Override
    public  Higher1> foldMap(R env, MonadDefer monad) {
      return monad.later(() -> provide(env));
    }

    @Override
    public ZIOModule getModule() {
      throw new UnsupportedOperationException();
    }

    @Override
    public String toString() {
      return "Redeem(" + current + ")";
    }
  }

  final class AccessM implements ZIO {

    private final Function1> function;

    protected AccessM(Function1> function) {
      this.function = requireNonNull(function);
    }

    @Override
    public Either provide(R env) {
      return function.apply(env).provide(env);
    }

    @Override
    public  Higher1> foldMap(R env, MonadDefer monad) {
      Higher1> later = monad.later(() -> function.apply(env));
      return monad.flatMap(later, zio -> zio.foldMap(env, monad));
    }

    @Override
    public ZIOModule getModule() {
      throw new UnsupportedOperationException();
    }

    @Override
    public String toString() {
      return "AccessM(?)";
    }
  }

  final class FoldM implements ZIO {

    private final ZIO current;
    private final Function1> nextError;
    private final Function1> next;

    protected FoldM(ZIO current, Function1> nextError, Function1> next) {
      this.current = requireNonNull(current);
      this.nextError = requireNonNull(nextError);
      this.next = requireNonNull(next);
    }

    @Override
    public Either provide(R env) {
      return current.provide(env).fold(nextError, next).provide(env);
    }

    @Override
    public  Higher1> foldMap(R env, MonadDefer monad) {
      Higher1> foldMap = current.foldMap(env, monad);
      Higher1> map = monad.map(foldMap, either -> either.fold(nextError, next));
      return monad.flatMap(map, zio -> zio.foldMap(env, monad));
    }

    @Override
    public ZIOModule getModule() {
      throw new UnsupportedOperationException();
    }

    @Override
    public String toString() {
      return "FoldM(" + current + ", ?, ?)";
    }
  }

  final class Bracket implements ZIO {

    private final ZIO acquire;
    private final Function1> use;
    private final Consumer1 release;

    protected Bracket(ZIO acquire,
                    Function1> use,
                    Consumer1 release) {
      this.acquire = requireNonNull(acquire);
      this.use = requireNonNull(use);
      this.release = requireNonNull(release);
    }

    @Override
    public Either provide(R env) {
      try (ZIOResource resource = new ZIOResource<>(acquire.provide(env), release)) {
        return resource.apply(use).provide(env);
      }
    }

    @Override
    public  Higher1> foldMap(R env, MonadDefer monad) {
      return monad.bracket(monad.flatMap(acquire.foldMap(env, monad), monad::fromEither),
                           use.andThen(zio -> zio.foldMap(env, monad)),
                           release);
    }

    @Override
    public ZIOModule getModule() {
      throw new UnsupportedOperationException();
    }

    @Override
    public String toString() {
      return "Bracket(" + acquire + ", ?, ?)";
    }
  }
}

interface ZIOModule {
  ZIO UNIT = ZIO.pure(Unit.unit());

  static  ZIO collapse(ZIO self) {
    ZIO current = self;
    while (true) {
      if (current instanceof ZIO.Suspend) {
        ZIO.Suspend suspend = (ZIO.Suspend) current;
        current = suspend.next();
      } else if (current instanceof ZIO.FlatMapped) {
        ZIO.FlatMapped flatMapped = (ZIO.FlatMapped) current;
        return new ZIO.FlatMapped<>(
            flatMapped::start,
            e -> collapse(flatMapped.run(Either.left(e))),
            a -> collapse(flatMapped.run(Either.right(a))));
      } else break;
    }
    return current;
  }

  @SuppressWarnings({"rawtypes", "unchecked"})
  static  Either evaluate(R env, ZIO self) {
    Stack> stack = new Stack<>();
    ZIO current = self;
    while (true) {
      if (current instanceof ZIO.FlatMapped) {
        ZIO.FlatMapped currentFlatMapped = (ZIO.FlatMapped) current;
        ZIO next = currentFlatMapped.start();
        if (next instanceof ZIO.FlatMapped) {
          ZIO.FlatMapped nextFlatMapped = (ZIO.FlatMapped) next;
          current = nextFlatMapped.start();
          stack.push(currentFlatMapped::run);
          stack.push(nextFlatMapped::run);
        } else {
          current = (ZIO) currentFlatMapped.run(next.provide(env));
        }
      } else if (!stack.isEmpty()) {
        current = (ZIO) stack.pop().apply(current.provide(env));
      } else break;
    }
    return current.provide(env);
  }

  static  ZIO repeat(ZIO self, UIO delay, int times) {
    return self.foldM(
        ZIO::raiseError, value -> {
          if (times > 0)
            return delay.toZIO().andThen(repeat(self, delay, times - 1));
          else
            return ZIO.pure(value);
        });
  }

  static  ZIO retry(ZIO self, UIO delay, int maxRetries) {
    return self.foldM(
        error -> {
          if (maxRetries > 0)
            return delay.toZIO().andThen(retry(self, delay.repeat(), maxRetries - 1));
          else
            return ZIO.raiseError(error);
        }, ZIO::pure);
  }
}

final class ZIOResource implements AutoCloseable {

  private final Either resource;
  private final Consumer1 release;

  ZIOResource(Either resource, Consumer1 release) {
    this.resource = requireNonNull(resource);
    this.release = requireNonNull(release);
  }

  public  ZIO apply(Function1> use) {
    return resource.map(use).fold(ZIO::raiseError, identity());
  }

  @Override
  public void close() {
    resource.toOption().ifPresent(release);
  }
}