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-2020, Antonio Gabriel Muñoz Conejo 
 * Distributed under the terms of the MIT License
 */
package com.github.tonivade.purefun.free;

import com.github.tonivade.purefun.Function1;
import com.github.tonivade.purefun.Higher1;
import com.github.tonivade.purefun.Higher2;
import com.github.tonivade.purefun.HigherKind;
import com.github.tonivade.purefun.Instance;
import com.github.tonivade.purefun.Kind;
import com.github.tonivade.purefun.type.Const;
import com.github.tonivade.purefun.typeclasses.Applicative;
import com.github.tonivade.purefun.typeclasses.FunctionK;
import com.github.tonivade.purefun.typeclasses.Monoid;

import java.util.Deque;
import java.util.LinkedList;

import static java.util.Collections.singletonList;
import static java.util.Objects.requireNonNull;

@HigherKind
public abstract class FreeAp {

  private FreeAp() {}

  public abstract  FreeAp map(Function1 mapper);

  public  FreeAp ap(FreeAp> apply) {
    if (apply instanceof Pure) {
      Pure> pure = (Pure>) apply;
      return map(pure.value);
    }
    return apply(this, apply);
  }

  public Higher1 fold(Applicative applicative) {
    return foldMap(FunctionK.identity(), applicative);
  }

  public  FreeAp compile(FunctionK transformer) {
    return foldMap(functionKF(transformer), applicativeF()).fix1(FreeAp::narrowK);
  }

  public  FreeAp flatCompile(
      FunctionK> functionK, Applicative> applicative) {
    return foldMap(functionK, applicative).fix1(FreeAp::narrowK);
  }

  public  M analyze(FunctionK> functionK, Applicative> applicative) {
    return foldMap(functionK, applicative).fix1(Const::narrowK).get();
  }

  public Free monad() {
    return foldMap(Free.functionKF(FunctionK.identity()), Free.monadF()).fix1(Free::narrowK);
  }

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

    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 {
        Higher1 argT = foldArg(argF, functionK, applicative);

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

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

          if (function.remaining > 1) {
            fns.addFirst(new CurriedFunction(res, function.remaining - 1));
          } else {
            if (fns.size() > 0) {
              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.size() > 0);
            }

            if (fns.size() == 0) {
              return (Higher1) res;
            }
          }
        } else {
          return (Higher1) argT;
        }
      }
    }
  }

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

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

  public static  FreeAp apply(FreeAp value, FreeAp> mapper) {
    return new FreeAp.Apply<>(value, mapper);
  }

  public static  FunctionK> functionKF(FunctionK functionK) {
    return new FunctionK>() {
      @Override
      public  Higher2 apply(Higher1 from) {
        return lift(functionK.apply(from)).kind2();
      }
    };
  }

  public static  Applicative> applicativeF() {
    return FreeApplicative.instance();
  }

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

  private static final class Pure extends FreeAp {

    private final A value;

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

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

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

  private static final class Lift extends FreeAp {

    private final Higher1 value;

    private Lift(Higher1 value) {
      this.value = requireNonNull(value);
    }

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

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

  private static final class Apply extends FreeAp {

    private final FreeAp value;
    private final FreeAp> apply;

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

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

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

  private static final class CurriedFunction {

    private final Higher1> value;
    private final int remaining;

    CurriedFunction(Higher1> value, int remaining) {
      this.value = requireNonNull(value);
      this.remaining = remaining;
    }
  }
}

@Instance
interface FreeApplicative extends Applicative> {

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

  @Override
  default  Higher2 ap(
      Higher1, T> value, Higher1, Function1> apply) {
    FreeAp freeAp = value.fix1(FreeAp::narrowK);
    FreeAp> apply1 = apply.fix1(FreeAp::narrowK);
    return FreeAp.apply(freeAp, apply1).kind2();
  }
}