package fj.data;
import fj.F;
import fj.P2;
import fj.Unit;
import fj.control.Trampoline;
import static fj.P.lazy;
import static fj.P.p;
import static fj.control.Trampoline.suspend;
import static fj.data.List.cons;
/**
* Created by MarkPerry on 7/07/2014.
*/
public final class State<S, A> {
public static <S, A> State<S, A> unit(F<S, P2<S, A>> runF) {
return new State<>(s -> Trampoline.pure(runF.f(s)));
}
public static <S> State<S, S> init() {
return unit(s -> dup(s));
}
public static <S> State<S, S> units(F<S, S> f) {
return unit(s -> dup(f.f(s)));
}
private static <S> P2<S, S> dup(S s) {
return p(s, s);
}
public static <S, A> State<S, A> constant(A a) {
return unit(s -> p(s, a));
}
public static <S, A> State<S, A> gets(F<S, A> f) {
return unit(s -> p(s, f.f(s)));
}
public static <S> State<S, Unit> put(S s) {
return unit(ignoredS -> p(s, Unit.unit()));
}
public static <S> State<S, Unit> modify(F<S, S> f) {
return unit(s -> p(f.f(s), Unit.unit()));
}
public static <S, A, B> State<S, B> flatMap(State<S, A> ts, F<A, State<S, B>> f) {
return ts.flatMap(f);
}
/**
* Evaluate each action in the sequence from left to right, and collect the results.
*/
public static <S, A> State<S, List<A>> sequence(List<State<S, A>> list) {
return list
.foldLeft(
(acc, ts) -> acc.flatMap(as -> ts.map(a -> cons(a, as))),
State.<S, List<A>>constant(List.nil()))
.map(as -> as.reverse());
}
/**
* Map each element of a structure to an action, evaluate these actions from left to right
* and collect the results.
*/
public static <S, A, B> State<S, List<B>> traverse(List<A> list, F<A, State<S, B>> f) {
return list
.foldLeft(
(acc, a) -> acc.flatMap(bs -> f.f(a).map(b -> cons(b, bs))),
State.<S, List<B>>constant(List.nil()))
.map(bs -> bs.reverse());
}
private static <S, A> State<S, A> suspended(F<S, Trampoline<P2<S, A>>> runF) {
return new State<>(s -> suspend(lazy(() -> runF.f(s))));
}
private final F<S, Trampoline<P2<S, A>>> runF;
private State(F<S, Trampoline<P2<S, A>>> runF) {
this.runF = runF;
}
public P2<S, A> run(S s) {
return runF.f(s).run();
}
public A eval(S s) {
return run(s)._2();
}
public S exec(S s) {
return run(s)._1();
}
public State<S, S> gets() {
return mapState(result -> p(result._1(), result._1()));
}
public <B> State<S, B> map(F<A, B> f) {
return mapState(result -> p(result._1(), f.f(result._2())));
}
public <B> State<S, B> mapState(F<P2<S, A>, P2<S, B>> f) {
return suspended(s -> runF.f(s).map(result -> f.f(result)));
}
public State<S, A> withs(F<S, S> f) {
return suspended(s -> runF.f(f.f(s)));
}
public <B> State<S, B> flatMap(F<A, State<S, B>> f) {
return suspended(s -> runF.f(s).bind(result -> Trampoline.pure(f.f(result._2()).run(result._1()))));
}
}