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

com.github.tonivade.purefun.monad.IO Maven / Gradle / Ivy

/*
 * Copyright (c) 2018-2020, Antonio Gabriel Muñoz Conejo 
 * Distributed under the terms of the MIT License
 */
package com.github.tonivade.purefun.monad;

import static com.github.tonivade.purefun.Function1.identity;
import static com.github.tonivade.purefun.Precondition.checkNonNull;

import java.time.Duration;
import java.util.Deque;
import java.util.LinkedList;
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.HigherKind;
import com.github.tonivade.purefun.Kind;
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.Witness;
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.Try;
import com.github.tonivade.purefun.typeclasses.MonadDefer;

@HigherKind(sealed = true)
public interface IO extends IOOf, Recoverable {

  T unsafeRunSync();

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

  default Future toFuture() {
    return toFuture(Future.DEFAULT_EXECUTOR);
  }

  default Future toFuture(Executor executor) {
    return Future.async(executor, this::unsafeRunSync);
  }

  default void safeRunAsync(Consumer1> callback) {
    safeRunAsync(Future.DEFAULT_EXECUTOR, callback);
  }

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

   Kind foldMap(MonadDefer monad);

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

  default  IO flatMap(Function1> map) {
    return new FlatMapped<>(Producer.cons(this), map);
  }

  default  IO andThen(IO after) {
    return flatMap(ignore -> after);
  }

  default  IO ap(IO> apply) {
    return apply.flatMap(this::map);
  }

  default IO> attempt() {
    return new Attempt<>(this);
  }

  default IO> either() {
    return attempt().map(Try::toEither);
  }

  default  IO> either(Function1 mapError, Function1 mapper) {
    return either().map(either -> either.bimap(mapError, mapper));
  }

  default  IO redeem(Function1 mapError, Function1 mapper) {
    return attempt().map(try_ -> try_.fold(mapError, mapper));
  }

  default  IO redeemWith(Function1> mapError, Function1> mapper) {
    return attempt().flatMap(try_ -> try_.fold(mapError, mapper));
  }

  default IO recover(Function1 mapError) {
    return redeem(mapError, identity());
  }

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

  default IO> timed() {
    return IO.task(() -> {
      long start = System.nanoTime();
      T result = unsafeRunSync();
      return Tuple.of(Duration.ofNanos(System.nanoTime() - start), result);
    });
  }

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

  default IO repeat(int times) {
    return IOModule.repeat(this, unit(), times);
  }

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

  default IO repeat(Duration delay, int times) {
    return IOModule.repeat(this, sleep(delay), times);
  }

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

  default IO retry(int maxRetries) {
    return IOModule.retry(this, unit(), maxRetries);
  }

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

  default IO retry(Duration delay, int maxRetries) {
    return IOModule.retry(this, sleep(delay), maxRetries);
  }

  static  IO pure(T value) {
    return new Pure<>(value);
  }

  static  IO raiseError(Throwable error) {
    return new Failure<>(error);
  }

  static  IO delay(Producer lazy) {
    return suspend(lazy.map(IO::pure));
  }

  static  IO suspend(Producer> lazy) {
    return new Suspend<>(lazy);
  }

  static  Function1> lift(Function1 task) {
    return task.andThen(IO::pure);
  }

  static IO sleep(Duration duration) {
    return new Sleep(duration);
  }

  static IO exec(CheckedRunnable task) {
    return task(() -> { task.run(); return Unit.unit(); });
  }

  static  IO task(Producer producer) {
    return new Task<>(producer);
  }

  static IO unit() {
    return IOModule.UNIT;
  }

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

  static  IO bracket(IO acquire, Function1> use) {
    return bracket(acquire, use, AutoCloseable::close);
  }

  static IO sequence(Sequence> sequence) {
    return sequence.fold(unit(), IO::andThen).andThen(unit());
  }

  static  IO> traverse(Sequence> sequence) {
    return sequence.foldLeft(pure(ImmutableList.empty()), 
        (IO> xs, IO a) -> map2(xs, a, Sequence::append));
  }

  static  IO map2(IO fa, IO fb, Function2 mapper) {
    return new Zip<>(fa, fb, mapper);
  }

  static  IO> tuple(IO fa, IO fb) {
    return map2(fa, fb, Tuple::of);
  }

  final class Pure implements SealedIO {

    private final T value;

    protected Pure(T value) {
      this.value = checkNonNull(value);
    }

    @Override
    public T unsafeRunSync() {
      return value;
    }

    @Override
    public  Kind foldMap(MonadDefer monad) {
      return monad.pure(value);
    }

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

  final class Zip implements SealedIO {
    
    private final IO left;
    private final IO right;
    private final Function2 mapper;

    protected Zip(IO left, IO right, Function2 mapper) {
      this.left = checkNonNull(left);
      this.right = checkNonNull(right);
      this.mapper = checkNonNull(mapper);
    }

    @Override
    public C unsafeRunSync() {
      return IOModule.evaluate(left.flatMap(a -> right.map(b -> mapper.apply(a, b))));
    }
    
    @Override
    public  Kind foldMap(MonadDefer monad) {
      return monad.map2(left.foldMap(monad), right.foldMap(monad), mapper);
    }
    
    @Override
    public String toString() {
      return "Zip(" + left + "," + right + "?)";
    }
  }

  final class FlatMapped implements SealedIO {

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

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

    @Override
    public R unsafeRunSync() {
      return IOModule.evaluate(this);
    }

    @Override
    public  Kind foldMap(MonadDefer monad) {
      return monad.flatMap(current.get().foldMap(monad), next.andThen(io -> io.foldMap(monad)));
    }

    @Override
    @SuppressWarnings("unchecked")
    public  IO flatMap(Function1> map) {
      return new FlatMapped<>(() -> (IO) start(), r -> new FlatMapped<>(() -> run((T) r), map::apply));
    }

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

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

    protected IO run(T value) {
      return next.apply(value);
    }
  }

  final class Failure implements SealedIO, Recoverable {

    private final Throwable error;

    protected Failure(Throwable error) {
      this.error = checkNonNull(error);
    }

    @Override
    public T unsafeRunSync() {
      return sneakyThrow(error);
    }

    @Override
    public  Kind foldMap(MonadDefer monad) {
      return monad.raiseError(error);
    }

    @Override
    @SuppressWarnings("unchecked")
    public  IO map(Function1 map) {
      return (IO) this;
    }

    @Override
    @SuppressWarnings("unchecked")
    public  IO flatMap(Function1> map) {
      return (IO) this;
    }

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

  final class Task implements SealedIO {

    private final Producer task;

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

    @Override
    public T unsafeRunSync() {
      return task.get();
    }

    @Override
    public  Kind foldMap(MonadDefer monad) {
      return monad.later(task);
    }

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

  final class Suspend implements SealedIO {

    private final Producer> lazy;

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

    @Override
    public T unsafeRunSync() {
      return IOModule.collapse(this).unsafeRunSync();
    }

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

    @Override
    public  Kind foldMap(MonadDefer monad) {
      return monad.defer(() -> lazy.get().foldMap(monad));
    }

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

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

  final class Sleep implements SealedIO, Recoverable {

    private final Duration duration;

    public Sleep(Duration duration) {
      this.duration = checkNonNull(duration);
    }

    @Override
    public Unit unsafeRunSync() {
      try {
        Thread.sleep(duration.toMillis());
        return Unit.unit();
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        return sneakyThrow(e);
      }
    }

    @Override
    public  Kind foldMap(MonadDefer monad) {
      return monad.sleep(duration);
    }

    @Override
    public String toString() {
      return "Sleep(" + duration + ')';
    }
  }

  final class Bracket implements SealedIO {

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

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

    @Override
    public R unsafeRunSync() {
      try (IOResource resource = new IOResource<>(IOModule.evaluate(acquire), release)) {
        return IOModule.evaluate(resource.apply(use));
      }
    }

    @Override
    public  Kind foldMap(MonadDefer monad) {
      return monad.bracket(acquire.foldMap(monad), use.andThen(io -> io.foldMap(monad)), release);
    }

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

  final class Attempt implements SealedIO> {

    private final IO current;

    protected Attempt(IO current) {
      this.current = checkNonNull(current);
    }

    @Override
    public Try unsafeRunSync() {
      return Try.of(() -> IOModule.evaluate(current));
    }

    @Override
    public  Kind> foldMap(MonadDefer monad) {
      return monad.map(monad.attempt(current.foldMap(monad)), Try::fromEither);
    }

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

interface IOModule {

  IO UNIT = IO.pure(Unit.unit());

  @SuppressWarnings("unchecked")
  static  IO collapse(IO self) {
    IO current = self;
    while (true) {
      if (current instanceof IO.Suspend) {
        IO.Suspend suspend = (IO.Suspend) current;
        current = suspend.next();
      } else if (current instanceof IO.FlatMapped) {
        IO.FlatMapped flatMapped = (IO.FlatMapped) current;
        return new IO.FlatMapped<>(flatMapped::start, a -> collapse(flatMapped.run(a)));
      } else break;
    }
    return current;
  }

  @SuppressWarnings({"rawtypes", "unchecked"})
  static  A evaluate(IO self) {
    Deque> stack = new LinkedList<>();
    IO current = self;
    while (true) {
      if (current instanceof IO.FlatMapped) {
        IO.FlatMapped currentFlatMapped = (IO.FlatMapped) current;
        IO next = currentFlatMapped.start();
        if (next instanceof IO.FlatMapped) {
          IO.FlatMapped nextFlatMapped = (IO.FlatMapped) next;
          current = nextFlatMapped.start();
          stack.push(currentFlatMapped::run);
          stack.push(nextFlatMapped::run);
        } else {
          current = currentFlatMapped.run(next.unsafeRunSync());
        }
      } else if (!stack.isEmpty()) {
        current = stack.pop().apply(current.unsafeRunSync());
      } else break;
    }
    return current.unsafeRunSync();
  }

  static  IO repeat(IO self, IO pause, int times) {
    return self.redeemWith(IO::raiseError, value -> {
      if (times > 0) {
        return pause.andThen(repeat(self, pause, times - 1));
      } else return IO.pure(value);
    });
  }

  static  IO retry(IO self, IO pause, int maxRetries) {
    return self.redeemWith(error -> {
      if (maxRetries > 0) {
        return pause.andThen(retry(self, pause.repeat(), maxRetries - 1));
      } else return IO.raiseError(error);
    }, IO::pure);
  }
}

final class IOResource implements AutoCloseable {

  private final T resource;
  private final Consumer1 release;

  IOResource(T resource, Consumer1 release) {
    this.resource = checkNonNull(resource);
    this.release = checkNonNull(release);
  }

  public  IO apply(Function1> use) {
    return use.apply(resource);
  }

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