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> 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