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