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> {
// 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<A> extends Trampoline<A> {
public abstract <R> R foldNormal(final F<A, R> pure, final F<P1<Trampoline<A>>, R> k);
public final <B> Trampoline<B> bind(final F<A, Trampoline<B>> 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<A> extends Trampoline<A> {
// The Normal subcomputation
private final Normal<Object> sub;
// The current continuation
private final F<Object, Trampoline<A>> cont;
private Codense(final Normal<Object> t, final F<Object, Trampoline<A>> k) {
sub = t;
cont = k;
}
public <R> R fold(final F<Normal<A>, R> n,
final F<Codense<A>, R> gs) {
return gs.f(this);
}
// The monadic bind constructs a new Codense whose subcomputation is still `sub`, and Kleisli-composes the
// continuations.
public <B> Trampoline<B> bind(final F<A, Trampoline<B>> 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<P1<Trampoline<A>>, 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<A> extends Normal<A> {
private final P1<Trampoline<A>> suspension;
private Suspend(final P1<Trampoline<A>> s) {
suspension = s;
}
public <R> R foldNormal(final F<A, R> pure, final F<P1<Trampoline<A>>, R> k) {
return k.f(suspension);
}
public <R> R fold(final F<Normal<A>, R> n, final F<Codense<A>, R> gs) {
return n.f(this);
}
public Either<P1<Trampoline<A>>, A> resume() {
return left(suspension);
}
}
// A pure value at the leaf of a computation.
private static final class Pure<A> extends Normal<A> {
private final A value;
private Pure(final A a) {
value = a;
}
public <R> R foldNormal(final F<A, R> pure, final F<P1<Trampoline<A>>, R> k) {
return pure.f(value);
}
public <R> R fold(final F<Normal<A>, R> n, final F<Codense<A>, R> gs) {
return n.f(this);
}
public Either<P1<Trampoline<A>>, A> resume() {
return right(value);
}
}
@SuppressWarnings("unchecked")
private static <A, B> Codense<B> codense(final Normal<A> a, final F<A, Trampoline<B>> k) {
return new Codense<>((Normal<Object>) a, (F<Object, Trampoline<B>>) k);
}
/**
* @return The first-class version of `pure`.
*/
public static <A> F<A, Trampoline<A>> 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 <A> Trampoline<A> 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 <A> Trampoline<A> suspend(final P1<Trampoline<A>> a) {
return new Suspend<>(a);
}
/**
* @return The first-class version of `suspend`.
*/
public static <A> F<P1<Trampoline<A>>, Trampoline<A>> suspend_() {
return Trampoline::suspend;
}
protected abstract <R> R fold(final F<Normal<A>, R> n, final F<Codense<A>, 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 <B> Trampoline<B> bind(final F<A, Trampoline<B>> 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 <B> Trampoline<B> map(final F<A, B> f) {
return bind(F1Functions.o(Trampoline.pure(), f));
}
/**
* @return The first-class version of `bind`.
*/
public static <A, B> F<F<A, Trampoline<B>>, F<Trampoline<A>, Trampoline<B>>> bind_() {
return f -> a -> a.bind(f);
}
/**
* @return The first-class version of `map`.
*/
public static <A, B> F<F<A, B>, F<Trampoline<A>, Trampoline<B>>> map_() {
return f -> a -> a.map(f);
}
/**
* @return The first-class version of `resume`.
*/
public static <A> F<Trampoline<A>, Either<P1<Trampoline<A>>, A>> resume_() {
return Trampoline::resume;
}
/**
* Runs a single step of this computation.
*
* @return The next step of this compuation.
*/
public abstract Either<P1<Trampoline<A>>, 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<A> current = this;
while (true) {
final Either<P1<Trampoline<A>>, A> x = current.resume();
for (final P1<Trampoline<A>> 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 <B> Trampoline<B> apply(final Trampoline<F<A, B>> 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 <B, C> Trampoline<C> bind(final Trampoline<B> lb, final F<A, F<B, C>> 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 <A, B, C> F<Trampoline<A>, F<Trampoline<B>, Trampoline<C>>> liftM2(final F<A, F<B, C>> 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 <B, C> Trampoline<C> zipWith(final Trampoline<B> b, final F2<A, B, C> f) {
final Either<P1<Trampoline<A>>, A> ea = resume();
final Either<P1<Trampoline<B>>, B> eb = b.resume();
for (final P1<Trampoline<A>> x : ea.left()) {
for (final P1<Trampoline<B>> 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<Trampoline<B>> 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.");
}
}