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

com.github.tonivade.purefun.effect.Schedule Maven / Gradle / Ivy

/*
 * Copyright (c) 2018-2022, 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.cons;
import static com.github.tonivade.purefun.Precondition.checkNonNull;

import java.time.Duration;

import com.github.tonivade.purefun.Function1;
import com.github.tonivade.purefun.Function2;
import com.github.tonivade.purefun.HigherKind;
import com.github.tonivade.purefun.Matcher1;
import com.github.tonivade.purefun.Operator1;
import com.github.tonivade.purefun.Tuple;
import com.github.tonivade.purefun.Tuple2;
import com.github.tonivade.purefun.Unit;
import com.github.tonivade.purefun.data.ImmutableList;
import com.github.tonivade.purefun.data.Sequence;
import com.github.tonivade.purefun.type.Either;

@HigherKind
public sealed interface Schedule extends ScheduleOf {

   Schedule map(Function1 mapper);

   Schedule contramap(Function1 comap);

  default  Schedule dimap(
      Function1 comap, Function1 map) {
    return this.contramap(comap).map(map);
  }

  default  Schedule as(C value) {
    return map(ignore -> value);
  }

  default Schedule unit() {
    return as(Unit.unit());
  }

  default Schedule andThen(Schedule next) {
    return andThenEither(next).map(Either::merge);
  }

   Schedule> andThenEither(Schedule next);

   Schedule> zip(Schedule other);

  default  Schedule zipLeft(Schedule other) {
    return zip(other).map(Tuple2::get1);
  }

  default  Schedule zipRight(Schedule other) {
    return zip(other).map(Tuple2::get2);
  }

   Schedule compose(Schedule other);

  default Schedule> collectAll() {
    return this.>fold(ImmutableList.empty(), Sequence::append);
  }

  default  Schedule fold(Z zero, Function2 next) {
    return foldM(zero, (z, b) -> PureIO.pure(next.apply(z, b)));
  }

   Schedule foldM(Z zero, Function2> next);

  default Schedule addDelay(Function1 map) {
    return addDelayM(map.andThen(URIO::pure));
  }

  Schedule addDelayM(Function1> map);

  default Schedule whileInput(Matcher1 condition) {
    return whileInputM(condition.asFunction().andThen(UIO::pure));
  }

  default Schedule whileInputM(Function1> condition) {
    return check((a, b) -> condition.apply(a));
  }

  default Schedule whileOutput(Matcher1 condition) {
    return whileOutputM(condition.asFunction().andThen(UIO::pure));
  }

  default Schedule whileOutputM(Function1> condition) {
    return check((a, b) -> condition.apply(b));
  }

  default Schedule untilInput(Matcher1 condition) {
    return untilInputM(condition.asFunction().andThen(UIO::pure));
  }

  Schedule untilInputM(Function1> condition);

  default Schedule untilOutput(Matcher1 condition) {
    return untilOutputM(condition.asFunction().andThen(UIO::pure));
  }

  Schedule untilOutputM(Function1> condition);

  Schedule check(Function2> condition);

  public static  Schedule once() {
    return Schedule.recurs(1).unit();
  }

  static  Schedule recurs(int times) {
    return Schedule.forever().whileOutput(x -> x < times);
  }

  static  Schedule spaced(Duration delay) {
    return Schedule.forever().addDelay(cons(delay));
  }

  static  Schedule linear(Duration delay) {
    return delayed(Schedule.forever().map(i -> delay.multipliedBy(i + 1L)));
  }

  static  Schedule exponential(Duration delay) {
    return exponential(delay, 2.0);
  }

  static  Schedule exponential(Duration delay, double factor) {
    return delayed(Schedule.forever().map(i -> delay.multipliedBy((long) Math.pow(factor, i.doubleValue()))));
  }

  static  Schedule delayed(Schedule schedule) {
    return schedule.addDelay(x -> x);
  }

  static  Schedule> recursSpaced(Duration delay, int times) {
    return Schedule.recurs(times).zip(Schedule.spaced(delay));
  }

  static  Schedule never() {
    return ScheduleImpl.of(
            URIO.unit(),
            (a, s) -> PureIO.raiseError(Unit.unit()),
            (a, s) -> s);
  }

  static  Schedule forever() {
    return unfold(0, a -> a + 1);
  }

  static  Schedule succeed(B value) {
    return Schedule.forever().as(value);
  }

  static  Schedule identity() {
    return ScheduleImpl.of(URIO.unit(),
            (a, s) -> PureIO.unit(),
            (a, s) -> a);
  }

  static  Schedule doWhile(Matcher1 condition) {
    return doWhileM(condition.asFunction().andThen(UIO::pure));
  }

  static  Schedule doWhileM(Function1> condition) {
    return Schedule.identity().whileInputM(condition);
  }

  static  Schedule doUntil(Matcher1 condition) {
    return doUntilM(condition.asFunction().andThen(UIO::pure));
  }

  static  Schedule doUntilM(Function1> condition) {
    return Schedule.identity().untilInputM(condition);
  }

  static  Schedule unfold(B initial, Operator1 next) {
    return unfoldM(URIO.pure(initial), next.andThen(PureIO::pure));
  }

  static  Schedule unfoldM(
          URIO initial, Function1> next) {
    return ScheduleImpl.of(initial, (a, s) -> next.apply(s), (a, s) -> s);
  }

  @FunctionalInterface
  interface Update {

    PureIO update(A last, S state);

  }

  @FunctionalInterface
  interface Extract {

    B extract(A last, S state);

  }
}

final class ScheduleImpl implements Schedule, Schedule.Update, Schedule.Extract {
  
  private final URIO initial;
  private final Update update;
  private final Extract extract;
  
  private ScheduleImpl(
      URIO initial, 
      Update update,
      Extract extract) {
    this.initial = checkNonNull(initial);
    this.update = checkNonNull(update);
    this.extract = checkNonNull(extract);
  }
  
  public URIO initial() {
    return initial;
  }
  
  @Override
  public PureIO update(A last, S state) {
    return update.update(last, state);
  }
  
  @Override
  public B extract(A last, S state) {
    return extract.extract(last, state);
  }

  @Override
  public  Schedule map(Function1 mapper) {
    return ScheduleImpl.of(
      initial, 
      update, 
      (a, s) -> mapper.apply(extract(a, s)));
  }

  @Override
  public  Schedule contramap(Function1 comap) {
    return ScheduleImpl.of(
      initial, 
      (c, s) -> update(comap.apply(c), s), 
      (c, s) -> extract(comap.apply(c), s));
  }

  @Override
  public Schedule andThen(Schedule next) {
    return andThenEither(next).map(Either::merge);
  }

  public  Schedule> andThenEither(Schedule next) {
    return doAndThenEither((ScheduleImpl) next);
  }

  @Override
  public  Schedule> zip(Schedule other) {
    return doZip((ScheduleImpl) other);
  }

  @Override
  public  Schedule compose(Schedule other) {
    return doCompose((ScheduleImpl) other);
  }

  @Override
  public  Schedule foldM(Z zero, Function2> next) {
    return ScheduleImpl.of(
      initial.map(s -> Tuple.of(s, zero)), 
      (a, sz) -> {
        PureIO update = update(a, sz.get1());
        PureIO other = next.apply(sz.get2(), extract(a, sz.get1()));
        return update.zip(other);
      }, 
      (a, sz) -> sz.get2());
  }

  @Override
  public Schedule addDelayM(Function1> map) {
    return updated(u -> (a, s) -> {
      PureIO> map2 = 
        PureIO.parMap2(
          map.apply(extract(a, s)).toPureIO(), 
          u.update(a, s), 
          Tuple::of);
      
      return map2.flatMap(ds -> {
        PureIO sleep = URIO.sleep(ds.get1()).toPureIO();
        return sleep.map(ignore -> ds.get2());
      });
    });
  }

  @Override
  public Schedule untilInputM(Function1> condition) {
    return updated(u -> (a, s) -> {
      UIO apply = condition.apply(a);
      return apply.toPureIO()
              .flatMap(test -> test ? PureIO.raiseError(Unit.unit()) : update(a, s));
    });
  }

  @Override
  public Schedule untilOutputM(Function1> condition) {
    return updated(u -> (a, s) -> {
      UIO apply = condition.apply(extract(a, s));
      return apply.toPureIO()
        .flatMap(test -> test ? PureIO.raiseError(Unit.unit()) : update(a, s));
    });
  }

  @Override
  public Schedule check(Function2> condition) {
    return updated(u -> (a, s) -> {
      PureIO apply = condition.apply(a, this.extract(a, s)).toPureIO();
      return apply.flatMap(result -> result != null && result ? u.update(a, s) : PureIO.raiseError(Unit.unit()));
    });
  }

  private  ScheduleImpl, A, Either> doAndThenEither(ScheduleImpl other) {
    return ScheduleImpl., A, Either>of(
            initial.map(Either::left),
            (a, st) -> st.fold(
                    s -> {
                      PureIO> orElse =
                              other.initial.toPureIO().flatMap(t -> other.update(a, t).map(Either::right));
                      return this.update(a, s).map(Either::left).orElse(orElse);
                    },
                    t -> other.update(a, t).map(Either::right)),
            (a, st) -> st.fold(
                    s -> Either.left(this.extract(a, s)),
                    t -> Either.right(other.extract(a, t))));
  }

  private  ScheduleImpl, A, Tuple2> doZip(ScheduleImpl other) {
    return ScheduleImpl., A, Tuple2>of(
            this.initial.zip(other.initial),
            (a, st) -> {
              PureIO self = this.update(a, st.get1());
              PureIO next = other.update(a, st.get2());
              return self.zip(next);
            },
            (a, st) -> Tuple.of(
                    this.extract(a, st.get1()),
                    other.extract(a, st.get2())));
  }

  private  ScheduleImpl, A, C> doCompose(ScheduleImpl other) {
    return ScheduleImpl., A, C>of(
            this.initial.zip(other.initial),
            (a, st) -> {
              PureIO self = this.update(a, st.get1());
              PureIO next = other.update(this.extract(a, st.get1()), st.get2());
              return self.zip(next);
            },
            (a, st) -> other.extract(this.extract(a, st.get1()), st.get2()));
  }

  private ScheduleImpl updated(Function1, Update> update) {
    return ScheduleImpl.of(initial, update.apply(this.update), this.extract);
  }
  
  public static  ScheduleImpl of(
      URIO initial, 
      Update update,
      Extract extract) {
    return new ScheduleImpl<>(initial, update, extract);
  }
}