package fj.data.optic;
import fj.F;
import fj.Function;
import fj.Monoid;
import fj.data.Either;
import fj.data.List;
import fj.data.Option;
/**
* A {@link Fold} can be seen as a {@link Getter} with many targets or a weaker {@link PTraversal} which cannot modify its
* target.
*
* {@link Fold} is on the top of the Optic hierarchy which means that {@link Getter}, {@link PTraversal}, {@link POptional},
* {@link PLens}, {@link PPrism} and {@link PIso} are valid {@link Fold}
*
* @param <S> the source of a {@link Fold}
* @param <A> the target of a {@link Fold}
*/
public abstract class Fold<S, A> {
/**
* map each target to a {@link Monoid} and combine the results underlying representation of {@link Fold}, all {@link Fold}
* methods are defined in terms of foldMap
*/
public abstract <M> F<S, M> foldMap(Monoid<M> m, F<A, M> f);
/** combine all targets using a target's {@link Monoid} */
public final F<S, A> fold(final Monoid<A> m) {
return foldMap(m, Function.identity());
}
/**
* get all the targets of a {@link Fold} TODO: Shall it return a Stream as there might be an infinite number of targets?
*/
public final List<A> getAll(final S s) {
return foldMap(Monoid.listMonoid(), List::single).f(s);
}
/** find the first target of a {@link Fold} matching the predicate */
public final F<S, Option<A>> find(final F<A, Boolean> p) {
return foldMap(Monoid.firstOptionMonoid(), a -> p.f(a) ? Option.some(a) : Option.none());
}
/** get the first target of a {@link Fold} */
public final Option<A> headOption(final S s) {
return find(Function.constant(Boolean.TRUE)).f(s);
}
/** check if at least one target satisfies the predicate */
public final F<S, Boolean> exist(final F<A, Boolean> p) {
return foldMap(Monoid.disjunctionMonoid, p);
}
/** check if all targets satisfy the predicate */
public final F<S, Boolean> all(final F<A, Boolean> p) {
return foldMap(Monoid.conjunctionMonoid, p);
}
/** join two {@link Fold} with the same target */
public final <S1> Fold<Either<S, S1>, A> sum(final Fold<S1, A> other) {
return new Fold<Either<S, S1>, A>() {
@Override
public <B> F<Either<S, S1>, B> foldMap(final Monoid<B> m, final F<A, B> f) {
return s -> s.either(Fold.this.foldMap(m, f), other.foldMap(m, f));
}
};
}
/**********************************************************/
/** Compose methods between a {@link Fold} and another Optics */
/**********************************************************/
/** compose a {@link Fold} with a {@link Fold} */
public final <B> Fold<S, B> composeFold(final Fold<A, B> other) {
return new Fold<S, B>() {
@Override
public <C> F<S, C> foldMap(final Monoid<C> m, final F<B, C> f) {
return Fold.this.foldMap(m, other.<C> foldMap(m, f));
}
};
}
/** compose a {@link Fold} with a {@link Getter} */
public final <C> Fold<S, C> composeGetter(final Getter<A, C> other) {
return composeFold(other.asFold());
}
/** compose a {@link Fold} with a {@link POptional} */
public final <B, C, D> Fold<S, C> composeOptional(final POptional<A, B, C, D> other) {
return composeFold(other.asFold());
}
/** compose a {@link Fold} with a {@link PPrism} */
public final <B, C, D> Fold<S, C> composePrism(final PPrism<A, B, C, D> other) {
return composeFold(other.asFold());
}
/** compose a {@link Fold} with a {@link PLens} */
public final <B, C, D> Fold<S, C> composeLens(final PLens<A, B, C, D> other) {
return composeFold(other.asFold());
}
/** compose a {@link Fold} with a {@link PIso} */
public final <B, C, D> Fold<S, C> composeIso(final PIso<A, B, C, D> other) {
return composeFold(other.asFold());
}
public static <A> Fold<A, A> id() {
return PIso.<A, A> pId().asFold();
}
public static <A> Fold<Either<A, A>, A> codiagonal() {
return new Fold<Either<A, A>, A>() {
@Override
public <B> F<Either<A, A>, B> foldMap(final Monoid<B> m, final F<A, B> f) {
return e -> e.either(f, f);
}
};
}
}