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 java.util.Objects.requireNonNull;
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.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.Sealed;
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.Sequence;
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 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.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);
}
Higher1 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> 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);
}
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.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 exec(() -> Thread.sleep(duration.toMillis()));
}
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());
}
final class Pure implements IO {
private final T value;
protected 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 Producer> current;
private final Function1> next;
protected FlatMapped(Producer> current, Function1> next) {
this.current = requireNonNull(current);
this.next = requireNonNull(next);
}
@Override
public R unsafeRunSync() {
return IOModule.evaluate(this);
}
@Override
public Higher1 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 IOModule getModule() {
throw new UnsupportedOperationException();
}
@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 IO, Recoverable {
private final Throwable error;
protected 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;
protected 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;
protected Suspend(Producer> lazy) {
this.lazy = requireNonNull(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 Higher1 foldMap(MonadDefer monad) {
return monad.defer(() -> lazy.get().foldMap(monad));
}
@Override
public IOModule getModule() {
throw new UnsupportedOperationException();
}
@Override
public String toString() {
return "Suspend(?)";
}
protected IO next() {
return lazy.get();
}
}
final class Bracket implements IO {
private final IO acquire;
private final Function1> use;
private final Consumer1 release;
protected 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 Attempt implements IO> {
private final IO current;
protected Attempt(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.attempt(current.foldMap(monad)), Try::fromEither);
}
@Override
public IOModule getModule() {
throw new UnsupportedOperationException();
}
@Override
public String toString() {
return "Attempt(" + current + ")";
}
}
}
interface IOModule {
IO UNIT = IO.pure(Unit.unit());
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 = (IO) currentFlatMapped.run(next.unsafeRunSync());
}
} else if (!stack.isEmpty()) {
current = (IO) 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 = 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