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-2021, 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(sealed = true)
public 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);

  }
}

abstract class ScheduleImpl implements SealedSchedule, Schedule.Update, Schedule.Extract {
  
  private ScheduleImpl() { }
  
  public abstract URIO initial();

  @Override
  public  Schedule map(Function1 mapper) {
    return ScheduleImpl.of(
      initial(), 
      this::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));
  }

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

  @SuppressWarnings("unchecked")
  public  Schedule> andThenEither(Schedule next) {
    return _andThenEither((ScheduleImpl) next);
  }

  @SuppressWarnings("unchecked")
  @Override
  public  Schedule> zip(Schedule other) {
    return _zip((ScheduleImpl) other);
  }

  @SuppressWarnings("unchecked")
  @Override
  public  Schedule compose(Schedule other) {
    return _compose((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()));
        PureIO> zip = update.zip(other);
        return zip;
      }, 
      (a, sz) -> sz.get2());
  }

  @Override
  public Schedule addDelayM(Function1> map) {
    return updated(update -> (a, s) -> {
      PureIO> map2 = 
        PureIO.parMap2(
          map.apply(extract(a, s)).toPureIO(), 
          update.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(update -> (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(update -> (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(update -> (a, s) -> {
      PureIO apply = condition.apply(a, this.extract(a, s)).toPureIO();
      return apply.flatMap(result -> result != null && result ? update.update(a, s) : PureIO.raiseError(Unit.unit()));
    });
  }

  private  ScheduleImpl, A, Either> _andThenEither(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> _zip(ScheduleImpl other) {
    return ScheduleImpl., A, Tuple2>of(
            this.initial().toPureIO().zip(other.initial().toPureIO()).toURIO(),
            (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> _compose(ScheduleImpl other) {
    return ScheduleImpl., A, C>of(
            this.initial().toPureIO().zip(other.initial().toPureIO()).toURIO(),
            (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) {
    checkNonNull(initial);
    checkNonNull(update);
    checkNonNull(extract);
    return new ScheduleImpl() {
      
      @Override
      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);
      }
    };
  }
}