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

com.github.tonivade.purefun.free.FreeAp Maven / Gradle / Ivy

/*
 * Copyright (c) 2018-2023, Antonio Gabriel Muñoz Conejo 
 * Distributed under the terms of the MIT License
 */
package com.github.tonivade.purefun.free;

import static com.github.tonivade.purefun.Precondition.checkNonNull;
import static com.github.tonivade.purefun.free.FreeApOf.toFreeAp;
import static com.github.tonivade.purefun.free.FreeOf.toFree;
import static com.github.tonivade.purefun.type.ConstOf.toConst;
import static java.util.Collections.singletonList;
import java.util.ArrayDeque;
import java.util.Deque;
import com.github.tonivade.purefun.Applicable;
import com.github.tonivade.purefun.Function1;
import com.github.tonivade.purefun.HigherKind;
import com.github.tonivade.purefun.Kind;
import com.github.tonivade.purefun.Witness;
import com.github.tonivade.purefun.type.Const_;
import com.github.tonivade.purefun.typeclasses.Applicative;
import com.github.tonivade.purefun.typeclasses.FunctionK;

@HigherKind
public sealed interface FreeAp extends FreeApOf, Applicable, A> {

  @Override
   FreeAp map(Function1 mapper);

  @Override
  default  FreeAp ap(Kind, Function1> apply) {
    if (apply instanceof Pure> pure) {
      return map(pure.value);
    }
    return apply(this, apply);
  }

  default Kind fold(Applicative applicative) {
    return foldMap(FunctionK.identity(), applicative);
  }

  default  FreeAp compile(FunctionK transformer) {
    return foldMap(functionKF(transformer), applicativeF()).fix(toFreeAp());
  }

  default  FreeAp flatCompile(
      FunctionK> functionK, Applicative> applicative) {
    return foldMap(functionK, applicative).fix(toFreeAp());
  }

  default  M analyze(FunctionK> functionK, Applicative> applicative) {
    return foldMap(functionK, applicative).fix(toConst()).get();
  }

  default Free monad() {
    return foldMap(Free.functionKF(FunctionK.identity()), Free.monadF()).fix(toFree());
  }

  @SuppressWarnings({"rawtypes", "unchecked"})
  default  Kind foldMap(FunctionK functionK, Applicative applicative) {
    Deque argsF = new ArrayDeque<>(singletonList(this));
    Deque fns = new ArrayDeque<>();

    while (true) {
      FreeAp argF = argsF.pollFirst();

      if (argF instanceof Apply) {
        int lengthInitial = argsF.size();

        do {
          Apply ap = (Apply) argF;
          argsF.addFirst(ap.value);
          argF = ap.apply;
        } while (argF instanceof Apply);

        int argc = argsF.size() - lengthInitial;
        fns.addFirst(new CurriedFunction(foldArg(argF, functionK, applicative), argc));
      } else {
        Kind argT = foldArg(argF, functionK, applicative);

        if (!fns.isEmpty()) {
          CurriedFunction function = fns.pollFirst();

          Kind res = applicative.ap(argT, function.value);

          if (function.remaining > 1) {
            fns.addFirst(new CurriedFunction(res, function.remaining - 1));
          } else {
            if (!fns.isEmpty()) {
              do {
                function = fns.pollFirst();

                res = applicative.ap(res, function.value);

                if (function.remaining > 1) {
                  fns.addFirst(new CurriedFunction(res, function.remaining - 1));
                }
              } while (function.remaining == 1 && !fns.isEmpty());
            }

            if (fns.isEmpty()) {
              return res;
            }
          }
        } else {
          return argT;
        }
      }
    }
  }

  static  FreeAp pure(T value) {
    return new FreeAp.Pure<>(value);
  }

  static  FreeAp lift(Kind value) {
    return new FreeAp.Lift<>(value);
  }

  static  FreeAp apply(Kind, ? extends T> value, 
      Kind, ? extends Function1> mapper) {
    return new FreeAp.Apply<>(value.fix(toFreeAp()), mapper.fix(toFreeAp()));
  }

  static  FunctionK> functionKF(FunctionK functionK) {
    return new FunctionK<>() {
      @Override
      public  FreeAp apply(Kind from) {
        return lift(functionK.apply(from));
      }
    };
  }

  @SuppressWarnings("unchecked")
  static  Applicative> applicativeF() {
    return FreeApplicative.INSTANCE;
  }

  private static  Kind foldArg(
      FreeAp argF, FunctionK transformation, Applicative applicative) {
    if (argF instanceof Pure pure) {
      return applicative.pure(pure.value);
    }
    if (argF instanceof Lift lift) {
      return transformation.apply(lift.value);
    }
    throw new IllegalStateException();
  }

  final class Pure implements FreeAp {

    private final A value;

    private Pure(A value) {
      this.value = checkNonNull(value);
    }

    @Override
    public  FreeAp map(Function1 mapper) {
      return pure(mapper.apply(value));
    }

    @Override
    public String toString() {
      return "Pure(" + value + ')';
    }
  }

  final class Lift implements FreeAp {

    private final Kind value;

    private Lift(Kind value) {
      this.value = checkNonNull(value);
    }

    @Override
    public  FreeAp map(Function1 mapper) {
      return apply(this, pure(mapper));
    }

    @Override
    public String toString() {
      return "Lift(" + value + ')';
    }
  }

  final class Apply implements FreeAp {

    private final FreeAp value;
    private final FreeAp> apply;

    private Apply(
        FreeAp value, 
        FreeAp> apply) {
      this.value = checkNonNull(value);
      this.apply = checkNonNull(apply);
    }

    @Override
    public  FreeAp map(Function1 mapper) {
      return apply(this, pure(mapper));
    }

    @Override
    public String toString() {
      return "Apply(" + value + ", ...)";
    }
  }

  record CurriedFunction(Kind> value, int remaining) {

    public CurriedFunction {
      checkNonNull(value);
    }
  }
}

interface FreeApplicative extends Applicative> {

  @SuppressWarnings("rawtypes")
  FreeApplicative INSTANCE = new FreeApplicative() {};

  @Override
  default  FreeAp pure(T value) {
    return FreeAp.pure(value);
  }

  @Override
  default  FreeAp ap(
      Kind, ? extends T> value, 
      Kind, ? extends Function1> apply) {
    return FreeAp.apply(value, apply);
  }
}