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-2019, 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 java.util.Objects.requireNonNull;

import java.util.concurrent.Executor;

import com.github.tonivade.purefun.CheckedConsumer1;
import com.github.tonivade.purefun.Consumer1;
import com.github.tonivade.purefun.Function1;
import com.github.tonivade.purefun.Higher1;
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.Unit;
import com.github.tonivade.purefun.concurrent.Future;
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
public interface IO extends 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.run(executor, this::unsafeRunSync);
  }

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

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

   Higher1 foldMap(MonadDefer monad);

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

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

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

  default IO> attemp() {
    return new Attemp<>(this);
  }

  default IO> either() {
    return attemp().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 attemp().map(try_ -> try_.fold(mapError, mapper));
  }

  default  IO redeemWith(Function1> mapError, Function1> mapper) {
    return attemp().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);
    });
  }

  IOModule getModule();

  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.andThen(IO::pure));
  }

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

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

  static IO exec(Runnable 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, CheckedConsumer1.of(AutoCloseable::close).unchecked());
  }

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

  final class Pure implements IO {

    private final T value;

    private Pure(T value) {
      this.value = requireNonNull(value);
    }

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

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

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

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

  final class FlatMapped implements IO {

    private final IO current;
    private final Function1> next;

    private FlatMapped(IO current, Function1> next) {
      this.current = requireNonNull(current);
      this.next = requireNonNull(next);
    }

    @Override
    public R unsafeRunSync() {
      return next.apply(current.unsafeRunSync()).unsafeRunSync();
    }

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

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

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

  final class Failure implements IO, Recoverable {

    private final Throwable error;

    private Failure(Throwable error) {
      this.error = requireNonNull(error);
    }

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

    @Override
    public  Higher1 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 IOModule getModule() {
      throw new UnsupportedOperationException();
    }

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

  final class Task implements IO {

    private final Producer task;

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

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

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

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

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

  final class Suspend implements IO {

    private final Producer> lazy;

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

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

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

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

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

  final class Bracket implements IO {

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

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

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

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

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

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

  final class Attemp implements IO> {

    private final IO current;

    private Attemp(IO current) {
      this.current = requireNonNull(current);
    }

    @Override
    public Try unsafeRunSync() {
      return Try.of(current::unsafeRunSync);
    }

    @Override
    public  Higher1> foldMap(MonadDefer monad) {
      return monad.map(monad.attemp(current.foldMap(monad)), either -> either.fold(Try::failure, Try::success));
    }

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

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

interface IOModule {
  IO UNIT = IO.pure(Unit.unit());
}

final class IOResource implements AutoCloseable {

  private final T resource;
  private final Consumer1 release;

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

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

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




© 2015 - 2025 Weber Informatics LLC | Privacy Policy