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

fj.control.Trampoline Maven / Gradle / Ivy

Go to download

Functional Java is an open source library that supports closures for the Java programming language

There is a newer version: 5.0
Show newest version
package fj.control;

import fj.*;
import fj.data.Either;

import static fj.Function.curry;
import static fj.data.Either.left;
import static fj.data.Either.right;

/**
 * A Trampoline is a potentially branching computation that can be stepped through and executed in constant stack.
 * It represent suspendable coroutines with subroutine calls, reified as a data structure.
 */
public abstract class Trampoline {

  // A Normal Trampoline is either done or suspended, and is allowed to be a subcomputation of a Codense.
  // This is the pointed functor part of the Trampoline monad.
  private abstract static class Normal extends Trampoline {
    public abstract  R foldNormal(final F pure, final F>, R> k);

    public final  Trampoline bind(final F> f) {
      return codense(this, f);
    }
  }

  // A Codense Trampoline delimits a subcomputation and tracks its current continuation. Subcomputations are only
  // allowed to be Normal, so all of the continuations accumulate on the right.
  private static final class Codense extends Trampoline {

    // The Normal subcomputation
    private final Normal sub;

    // The current continuation
    private final F> cont;

    private Codense(final Normal t, final F> k) {
      sub = t;
      cont = k;
    }

    public  R fold(final F, R> n,
                      final F, R> gs) {
      return gs.f(this);
    }

    // The monadic bind constructs a new Codense whose subcomputation is still `sub`, and Kleisli-composes the
    // continuations.
    public  Trampoline bind(final F> f) {
      return codense(sub, o -> suspend(P.lazy(() -> cont.f(o).bind(f))));
    }

    // The resumption of a Codense is the resumption of its subcomputation. If that computation is done, its result
    // gets shifted into the continuation.
    public Either>, A> resume() {
      return left(sub.resume().either(p -> p.map(ot ->
          ot.fold(
              o -> o.foldNormal(cont, t -> t._1().bind(cont)),
              c -> codense(c.sub, o -> c.cont.f(o).bind(cont))
          )
      ), o -> P.lazy(() -> cont.f(o))));
    }
  }

  // A suspended computation that can be resumed.
  private static final class Suspend extends Normal {

    private final P1> suspension;

    private Suspend(final P1> s) {
      suspension = s;
    }

    public  R foldNormal(final F pure, final F>, R> k) {
      return k.f(suspension);
    }

    public  R fold(final F, R> n, final F, R> gs) {
      return n.f(this);
    }

    public Either>, A> resume() {
      return left(suspension);
    }
  }

  // A pure value at the leaf of a computation.
  private static final class Pure extends Normal {
    private final A value;

    private Pure(final A a) {
      value = a;
    }

    public  R foldNormal(final F pure, final F>, R> k) {
      return pure.f(value);
    }

    public  R fold(final F, R> n, final F, R> gs) {
      return n.f(this);
    }

    public Either>, A> resume() {
      return right(value);
    }
  }

  @SuppressWarnings("unchecked")
  private static  Codense codense(final Normal a, final F> k) {
    return new Codense<>((Normal) a, (F>) k);
  }

  /**
   * @return The first-class version of `pure`.
   */
  public static  F> pure() {
    return Trampoline::pure;
  }

  /**
   * Constructs a pure computation that results in the given value.
   *
   * @param a The value of the result.
   * @return A trampoline that results in the given value.
   */
  public static  Trampoline pure(final A a) {
    return new Pure<>(a);
  }

  /**
   * Suspends the given computation in a thunk.
   *
   * @param a A trampoline suspended in a thunk.
   * @return A trampoline whose next step runs the given thunk.
   */
  public static  Trampoline suspend(final P1> a) {
    return new Suspend<>(a);
  }

  /**
   * @return The first-class version of `suspend`.
   */
  public static  F>, Trampoline> suspend_() {
    return Trampoline::suspend;
  }

  protected abstract  R fold(final F, R> n, final F, R> gs);

  /**
   * Binds the given continuation to the result of this trampoline.
   *
   * @param f A function that constructs a trampoline from the result of this trampoline.
   * @return A new trampoline that runs this trampoline, then continues with the given function.
   */
  public abstract  Trampoline bind(final F> f);

  /**
   * Maps the given function across the result of this trampoline.
   *
   * @param f A function that gets applied to the result of this trampoline.
   * @return A new trampoline that runs this trampoline, then applies the given function to the result.
   */
  public final  Trampoline map(final F f) {
    return bind(F1Functions.o(Trampoline.pure(), f));
  }

  /**
   * @return The first-class version of `bind`.
   */
  public static  F>, F, Trampoline>> bind_() {
    return f -> a -> a.bind(f);
  }

  /**
   * @return The first-class version of `map`.
   */
  public static  F, F, Trampoline>> map_() {
    return f -> a -> a.map(f);
  }

  /**
   * @return The first-class version of `resume`.
   */
  public static  F, Either>, A>> resume_() {
    return Trampoline::resume;
  }

  /**
   * Runs a single step of this computation.
   *
   * @return The next step of this compuation.
   */
  public abstract Either>, A> resume();

  /**
   * Runs this computation all the way to the end, in constant stack.
   *
   * @return The end result of this computation.
   */
  @SuppressWarnings("LoopStatementThatDoesntLoop")
  public final A run() {
    Trampoline current = this;
    while (true) {
      final Either>, A> x = current.resume();
      for (final P1> t : x.left()) {
        current = t._1();
      }
      for (final A a : x.right()) {
        return a;
      }
    }
  }

  /**
   * Performs function application within a Trampoline (applicative functor pattern).
   *
   * @param lf A Trampoline resulting in the function to apply.
   * @return A new Trampoline after applying the given function through this Trampoline.
   */
  public final  Trampoline apply(final Trampoline> lf) {
    return lf.bind(this::map);
  }

  /**
   * Binds the given function across the result of this Trampoline and the given Trampoline.
   *
   * @param lb A given Trampoline to bind the given function with.
   * @param f  The function to combine the results of this Trampoline and the given Trampoline.
   * @return A new Trampoline combining the results of the two trampolines with the given function.
   */
  public final  Trampoline bind(final Trampoline lb, final F> f) {
    return lb.apply(map(f));
  }


  /**
   * Promotes the given function of arity-2 to a function on Trampolines.
   *
   * @param f The function to promote to a function on Trampolines.
   * @return The given function, promoted to operate on Trampolines.
   */
  public static  F, F, Trampoline>> liftM2(final F> f) {
    return curry((as, bs) -> as.bind(bs, f));
  }

  /**
   * Combines two trampolines so they run cooperatively. The results are combined with the given function.
   *
   * @param b Another trampoline to combine with this trampoline.
   * @param f A function to combine the results of the two trampolines.
   * @return A new trampoline that runs this trampoline and the given trampoline simultaneously.
   */
  @SuppressWarnings("LoopStatementThatDoesntLoop")
  public final  Trampoline zipWith(final Trampoline b, final F2 f) {
    final Either>, A> ea = resume();
    final Either>, B> eb = b.resume();
    for (final P1> x : ea.left()) {
      for (final P1> y : eb.left()) {
        return suspend(x.bind(y, F2Functions.curry((ta, tb) -> suspend(P.lazy(() -> ta.zipWith(tb, f))))));
      }
      for (final B y : eb.right()) {
        return suspend(x.map(ta -> ta.map(F2Functions.f(F2Functions.flip(f), y))));
      }
    }
    for (final A x : ea.right()) {
      for (final B y : eb.right()) {
        return suspend(P.lazy(() -> pure(f.f(x, y))));
      }
      for (final P1> y : eb.left()) {
        return suspend(y.map(liftM2(F2Functions.curry(f)).f(pure(x))));
      }
    }
    throw Bottom.error("Match error: Trampoline is neither done nor suspended.");
  }
}