package fj.data; import fj.F; import fj.F0; import fj.F1Functions; import fj.Function; import fj.P; import fj.P2; import fj.Unit; /** * */ public final class Iteratee { /** The input to an iteratee. */ public abstract static class Input<E> { Input() {} // sealed public abstract <Z> Z apply(final F0<Z> empty, final F0<F<E, Z>> el, final F0<Z> eof); /** Input that has no values available */ public static <E> Input<E> empty() { return new Input<E>() { @Override public <Z> Z apply(final F0<Z> empty, final F0<F<E, Z>> el, final F0<Z> eof) { return empty.f(); } }; } /** Input that is exhausted */ public static <E> Input<E> eof() { return new Input<E>() { @Override public <Z> Z apply(final F0<Z> empty, final F0<F<E, Z>> el, final F0<Z> eof) { return eof.f(); } }; } /** Input that has a value available */ public static <E> Input<E> el(final E element) { return new Input<E>() { @Override public <Z> Z apply(final F0<Z> empty, final F0<F<E, Z>> el, final F0<Z> eof) { return el.f().f(element); } }; } } /** A pure iteratee computation which is either done or needs more input */ public abstract static class IterV<E, A> { IterV() {} // sealed /** A computation that takes an element from an input to yield a new computation */ public static <E, A> IterV<E, A> cont(final F<Input<E>, IterV<E, A>> f) { return new IterV<E, A>() { @Override public <Z> Z fold(final F<P2<A, Input<E>>, Z> done, final F<F<Input<E>, IterV<E, A>>, Z> cont) { return cont.f(f); } }; } public abstract <Z> Z fold(final F<P2<A, Input<E>>, Z> done, final F<F<Input<E>, IterV<E, A>>, Z> cont); /** A computation that has finished */ public static <E, A> IterV<E, A> done(final A a, final Input<E> i) { final P2<A, Input<E>> p = P.p(a, i); return new IterV<E, A>() { @Override public <Z> Z fold(final F<P2<A, Input<E>>, Z> done, final F<F<Input<E>, IterV<E, A>>, Z> cont) { return done.f(p); } }; } public final A run() { final F<IterV<E, A>, Option<A>> runCont = new F<IterV<E, A>, Option<A>>() { final F<P2<A, Input<E>>, Option<A>> done = F1Functions.andThen(P2.__1(), Option.some_()); final F<F<Input<E>, IterV<E, A>>, Option<A>> cont = Function.constant(Option.none()); @Override public Option<A> f(final IterV<E, A> i) { return i.fold(done, cont); } }; final F<P2<A, Input<E>>, A> done = P2.__1(); final F<F<Input<E>, IterV<E, A>>, A> cont = k -> runCont.f(k.f(Input.eof())).valueE("diverging iteratee"); return fold(done, cont); } /** TODO more documentation */ public final <B> IterV<E, B> bind(final F<A, IterV<E, B>> f) { final F<P2<A, Input<E>>, IterV<E, B>> done = xe -> { final Input<E> e = xe._2(); final F<P2<B, Input<E>>, IterV<E, B>> done1 = y_ -> { final B y = y_._1(); return done(y, e); }; final F<F<Input<E>, IterV<E, B>>, IterV<E, B>> cont = k -> k.f(e); final A x = xe._1(); return f.f(x).fold(done1, cont); }; final F<F<Input<E>, IterV<E, A>>, IterV<E, B>> cont = k -> cont(e -> k.f(e).bind(f)); return this.fold(done, cont); } /** An iteratee that counts and consumes the elements of the input */ public static <E> IterV<E, Integer> length() { final F<Integer, F<Input<E>, IterV<E, Integer>>> step = new F<Integer, F<Input<E>, IterV<E, Integer>>>() { final F<Integer, F<Input<E>, IterV<E, Integer>>> step = this; @Override public F<Input<E>, IterV<E, Integer>> f(final Integer acc) { final F0<IterV<E, Integer>> empty = () -> cont(step.f(acc)); final F0<F<E, IterV<E, Integer>>> el = () -> P.p(cont(step.f(acc + 1))).constant(); final F0<IterV<E, Integer>> eof = () -> done(acc, Input.<E>eof()); return s -> s.apply(empty, el, eof); } }; return cont(step.f(0)); } /** An iteratee that skips the first n elements of the input */ public static <E> IterV<E, Unit> drop(final int n) { final F<Input<E>, IterV<E, Unit>> step = new F<Input<E>, IterV<E, Unit>>() { final F<Input<E>, IterV<E, Unit>> step = this; final F0<IterV<E, Unit>> empty = () -> cont(step); final F0<F<E, IterV<E, Unit>>> el = () -> P.p(IterV.<E>drop(n - 1)).constant(); final F0<IterV<E, Unit>> eof = () -> done(Unit.unit(), Input.<E>eof()); @Override public IterV<E, Unit> f(final Input<E> s) { return s.apply(empty, el, eof); } }; return n == 0 ? done(Unit.unit(), Input.empty()) : cont(step); } /** An iteratee that consumes the head of the input */ public static <E> IterV<E, Option<E>> head() { final F<Input<E>, IterV<E, Option<E>>> step = new F<Input<E>, IterV<E, Option<E>>>() { final F<Input<E>, IterV<E, Option<E>>> step = this; final F0<IterV<E, Option<E>>> empty = () -> cont(step); final F0<F<E, IterV<E, Option<E>>>> el = () -> e -> done(Option.some(e), Input.<E>empty()); final F0<IterV<E, Option<E>>> eof = () -> done(Option.<E>none(), Input.<E>eof()); @Override public IterV<E, Option<E>> f(final Input<E> s) { return s.apply(empty, el, eof); } }; return cont(step); } /** An iteratee that returns the first element of the input */ public static <E> IterV<E, Option<E>> peek() { final F<Input<E>, IterV<E, Option<E>>> step = new F<Input<E>, IterV<E, Option<E>>>() { final F<Input<E>, IterV<E, Option<E>>> step = this; final F0<IterV<E, Option<E>>> empty = () -> cont(step); final F0<F<E, IterV<E, Option<E>>>> el = () -> e -> done(Option.some(e), Input.el(e)); final F0<IterV<E, Option<E>>> eof = () -> done(Option.<E>none(), Input.<E>eof()); @Override public IterV<E, Option<E>> f(final Input<E> s) { return s.apply(empty, el, eof); } }; return cont(step); } /** An iteratee that consumes the input elements and returns them as a list in reverse order, * so that the last line is the first element. This allows to build a list from 2 iteratees. */ public static <E> IterV<E, List<E>> list() { final F<List<E>, F<Input<E>, IterV<E, List<E>>>> step = new F<List<E>, F<Input<E>, IterV<E, List<E>>>>() { final F<List<E>, F<Input<E>, IterV<E, List<E>>>> step = this; @Override public F<Input<E>, IterV<E, List<E>>> f(final List<E> acc) { final F0<IterV<E, List<E>>> empty = () -> cont(step.f(acc)); final F0<F<E, IterV<E, List<E>>>> el = () -> e -> cont(step.f(acc.cons(e))); final F0<IterV<E, List<E>>> eof = () -> done(acc, Input.<E>eof()); return s -> s.apply(empty, el, eof); } }; return cont(step.f(List.nil())); } } private Iteratee() { throw new UnsupportedOperationException(); } }