package fj.data.optic;
import fj.F;
import fj.Function;
import fj.Monoid;
import fj.P;
import fj.P1;
import fj.Semigroup;
import fj.control.Trampoline;
import fj.control.parallel.Promise;
import fj.control.parallel.Strategy;
import fj.data.Either;
import fj.data.IO;
import fj.data.IOFunctions;
import fj.data.List;
import fj.data.Option;
import fj.data.Stream;
import fj.data.Validation;
import fj.data.vector.V2;
/**
* A {@link PPrism} can be seen as a pair of functions: - `getOrModify: S => T \/ A` - `reverseGet : B => T`
*
* A {@link PPrism} could also be defined as a weaker {@link PIso} where get can fail.
*
* Typically a {@link PPrism} or {@link Prism} encodes the relation between a Sum or CoProduct type (e.g. sealed trait) and one
* of it is element.
*
* {@link PPrism} stands for Polymorphic Prism as it set and modify methods change a type `A` to `B` and `S` to `T`.
* {@link Prism} is a {@link PPrism} where the type of target cannot be modified.
*
* A {@link PPrism} is also a valid {@link Fold}, {@link POptional}, {@link PTraversal} and {@link PSetter}
*
* @param <S> the source of a {@link PPrism}
* @param <T> the modified source of a {@link PPrism}
* @param <A> the target of a {@link PPrism}
* @param <B> the modified target of a {@link PPrism}
*/
public abstract class PPrism<S, T, A, B> {
PPrism() {
super();
}
/** get the target of a {@link PPrism} or modify the source in case there is no target */
public abstract Either<T, A> getOrModify(S s);
/** get the modified source of a {@link PPrism} */
public abstract T reverseGet(B b);
/** get the target of a {@link PPrism} or nothing if there is no target */
public abstract Option<A> getOption(final S s);
/** modify polymorphically the target of a {@link PPrism} with an Applicative function */
public final <C> F<S, F<C, T>> modifyFunctionF(final F<A, F<C, B>> f) {
return s -> getOrModify(s).either(
Function.constant(),
a -> Function.compose(this::reverseGet, f.f(a))
);
}
/** modify polymorphically the target of a {@link PPrism} with an Applicative function */
public final <L> F<S, Either<L, T>> modifyEitherF(final F<A, Either<L, B>> f) {
return s -> getOrModify(s).either(
Either.right_(),
t -> f.f(t).right().map(this::reverseGet)
);
}
/** modify polymorphically the target of a {@link PPrism} with an Applicative function */
public final F<S, IO<T>> modifyIOF(final F<A, IO<B>> f) {
return s -> getOrModify(s).either(
IOFunctions::unit,
t -> IOFunctions.map(f.f(t), this::reverseGet)
);
}
/** modify polymorphically the target of a {@link PPrism} with an Applicative function */
public final F<S, Trampoline<T>> modifyTrampolineF(final F<A, Trampoline<B>> f) {
return s -> getOrModify(s).either(
Trampoline.pure(),
t -> f.f(t).map(this::reverseGet)
);
}
/** modify polymorphically the target of a {@link PPrism} with an Applicative function */
public final F<S, Promise<T>> modifyPromiseF(final F<A, Promise<B>> f) {
return s -> getOrModify(s).either(
t -> Promise.promise(Strategy.idStrategy(), P.p(t)),
t -> f.f(t).fmap(this::reverseGet)
);
}
/** modify polymorphically the target of a {@link PPrism} with an Applicative function */
public final F<S, List<T>> modifyListF(final F<A, List<B>> f) {
return s -> getOrModify(s).either(
List::single,
t -> f.f(t).map(this::reverseGet)
);
}
/** modify polymorphically the target of a {@link PPrism} with an Applicative function */
public final F<S, Option<T>> modifyOptionF(final F<A, Option<B>> f) {
return s -> getOrModify(s).either(
Option.some_(),
t -> f.f(t).map(this::reverseGet)
);
}
/** modify polymorphically the target of a {@link PPrism} with an Applicative function */
public final F<S, Stream<T>> modifyStreamF(final F<A, Stream<B>> f) {
return s -> getOrModify(s).either(
Stream.single(),
t -> f.f(t).map(this::reverseGet)
);
}
/** modify polymorphically the target of a {@link PPrism} with an Applicative function */
public final F<S, P1<T>> modifyP1F(final F<A, P1<B>> f) {
return s -> getOrModify(s).either(
P.p1(),
t -> f.f(t).map(this::reverseGet)
);
}
/** modify polymorphically the target of a {@link PPrism} with an Applicative function */
public final <E> F<S, Validation<E, T>> modifyValidationF(final F<A, Validation<E, B>> f) {
return s -> getOrModify(s).either(
Validation::<E, T>success,
t -> f.f(t).map(this::reverseGet)
);
}
/** modify polymorphically the target of a {@link PPrism} with an Applicative function */
public final F<S, V2<T>> modifyV2F(final F<A, V2<B>> f) {
return s -> getOrModify(s).either(
t -> V2.p(P.p(t, t)),
t -> f.f(t).map(this::reverseGet)
);
}
/** modify polymorphically the target of a {@link PPrism} with a function */
public final F<S, T> modify(final F<A, B> f) {
return s -> getOrModify(s).either(Function.identity(), a -> reverseGet(f.f(a)));
}
/** modify polymorphically the target of a {@link PPrism} with a function. return empty if the {@link PPrism} is not matching */
public final F<S, Option<T>> modifyOption(final F<A, B> f) {
return s -> getOption(s).map(a -> reverseGet(f.f(a)));
}
/** set polymorphically the target of a {@link PPrism} with a value */
public final F<S, T> set(final B b) {
return modify(Function.constant(b));
}
/** set polymorphically the target of a {@link PPrism} with a value. return empty if the {@link PPrism} is not matching */
public final F<S, Option<T>> setOption(final B b) {
return modifyOption(Function.constant(b));
}
/** check if a {@link PPrism} has a target */
public final boolean isMatching(final S s) {
return getOption(s).isSome();
}
/** create a {@link Getter} from the modified target to the modified source of a {@link PPrism} */
public final Getter<B, T> re() {
return Getter.getter(this::reverseGet);
}
/************************************************************/
/** Compose methods between a {@link PPrism} and another Optics */
/************************************************************/
/** compose a {@link PPrism} with a {@link Fold} */
public final <C> Fold<S, C> composeFold(final Fold<A, C> other) {
return asFold().composeFold(other);
}
/** compose a {@link PPrism} with a {@link Getter} */
public final <C> Fold<S, C> composeGetter(final Getter<A, C> other) {
return asFold().composeGetter(other);
}
/** compose a {@link PPrism} with a {@link PSetter} */
public final <C, D> PSetter<S, T, C, D> composeSetter(final PSetter<A, B, C, D> other) {
return asSetter().composeSetter(other);
}
/** compose a {@link PPrism} with a {@link PTraversal} */
public final <C, D> PTraversal<S, T, C, D> composeTraversal(final PTraversal<A, B, C, D> other) {
return asTraversal().composeTraversal(other);
}
/** compose a {@link PPrism} with a {@link POptional} */
public final <C, D> POptional<S, T, C, D> composeOptional(final POptional<A, B, C, D> other) {
return asOptional().composeOptional(other);
}
/** compose a {@link PPrism} with a {@link PLens} */
public final <C, D> POptional<S, T, C, D> composeLens(final PLens<A, B, C, D> other) {
return asOptional().composeOptional(other.asOptional());
}
/** compose a {@link PPrism} with a {@link PPrism} */
public final <C, D> PPrism<S, T, C, D> composePrism(final PPrism<A, B, C, D> other) {
return new PPrism<S, T, C, D>() {
@Override
public Either<T, C> getOrModify(final S s) {
return PPrism.this.getOrModify(s).right()
.bind(a -> other.getOrModify(a).bimap(b -> PPrism.this.set(b).f(s), Function.identity()));
}
@Override
public T reverseGet(final D d) {
return PPrism.this.reverseGet(other.reverseGet(d));
}
@Override
public Option<C> getOption(final S s) {
return PPrism.this.getOption(s).bind(other::getOption);
}
};
}
/** compose a {@link PPrism} with a {@link PIso} */
public final <C, D> PPrism<S, T, C, D> composeIso(final PIso<A, B, C, D> other) {
return composePrism(other.asPrism());
}
/******************************************************************/
/** Transformation methods to view a {@link PPrism} as another Optics */
/******************************************************************/
/** view a {@link PPrism} as a {@link Fold} */
public final Fold<S, A> asFold() {
return new Fold<S, A>() {
@Override
public <M> F<S, M> foldMap(final Monoid<M> monoid, final F<A, M> f) {
return s -> getOption(s).map(f).orSome(monoid.zero());
}
};
}
/** view a {@link PPrism} as a {@link Setter} */
public PSetter<S, T, A, B> asSetter() {
return new PSetter<S, T, A, B>() {
@Override
public F<S, T> modify(final F<A, B> f) {
return PPrism.this.modify(f);
}
@Override
public F<S, T> set(final B b) {
return PPrism.this.set(b);
}
};
}
/** view a {@link PPrism} as a {@link PTraversal} */
public PTraversal<S, T, A, B> asTraversal() {
final PPrism<S, T, A, B> self = this;
return new PTraversal<S, T, A, B>() {
@Override
public <C> F<S, F<C, T>> modifyFunctionF(final F<A, F<C, B>> f) {
return self.modifyFunctionF(f);
}
@Override
public <L> F<S, Either<L, T>> modifyEitherF(final F<A, Either<L, B>> f) {
return self.modifyEitherF(f);
}
@Override
public F<S, IO<T>> modifyIOF(final F<A, IO<B>> f) {
return self.modifyIOF(f);
}
@Override
public F<S, Trampoline<T>> modifyTrampolineF(final F<A, Trampoline<B>> f) {
return self.modifyTrampolineF(f);
}
@Override
public F<S, Promise<T>> modifyPromiseF(final F<A, Promise<B>> f) {
return self.modifyPromiseF(f);
}
@Override
public F<S, List<T>> modifyListF(final F<A, List<B>> f) {
return self.modifyListF(f);
}
@Override
public F<S, Option<T>> modifyOptionF(final F<A, Option<B>> f) {
return self.modifyOptionF(f);
}
@Override
public F<S, Stream<T>> modifyStreamF(final F<A, Stream<B>> f) {
return self.modifyStreamF(f);
}
@Override
public F<S, P1<T>> modifyP1F(final F<A, P1<B>> f) {
return self.modifyP1F(f);
}
@Override
public <E> F<S, Validation<E, T>> modifyValidationF(Semigroup<E> s, final F<A, Validation<E, B>> f) {
return self.modifyValidationF(f);
}
@Override
public F<S, V2<T>> modifyV2F(final F<A, V2<B>> f) {
return self.modifyV2F(f);
}
@Override
public <M> F<S, M> foldMap(final Monoid<M> monoid, final F<A, M> f) {
return s -> getOption(s).map(f).orSome(monoid.zero());
}
};
}
/** view a {@link PPrism} as a {@link POptional} */
public POptional<S, T, A, B> asOptional() {
final PPrism<S, T, A, B> self = this;
return new POptional<S, T, A, B>() {
@Override
public Either<T, A> getOrModify(final S s) {
return self.getOrModify(s);
}
@Override
public <C> F<S, F<C, T>> modifyFunctionF(final F<A, F<C, B>> f) {
return self.modifyFunctionF(f);
}
@Override
public <L> F<S, Either<L, T>> modifyEitherF(final F<A, Either<L, B>> f) {
return self.modifyEitherF(f);
}
@Override
public F<S, IO<T>> modifyIOF(final F<A, IO<B>> f) {
return self.modifyIOF(f);
}
@Override
public F<S, Trampoline<T>> modifyTrampolineF(final F<A, Trampoline<B>> f) {
return self.modifyTrampolineF(f);
}
@Override
public F<S, Promise<T>> modifyPromiseF(final F<A, Promise<B>> f) {
return self.modifyPromiseF(f);
}
@Override
public F<S, List<T>> modifyListF(final F<A, List<B>> f) {
return self.modifyListF(f);
}
@Override
public F<S, Option<T>> modifyOptionF(final F<A, Option<B>> f) {
return self.modifyOptionF(f);
}
@Override
public F<S, Stream<T>> modifyStreamF(final F<A, Stream<B>> f) {
return self.modifyStreamF(f);
}
@Override
public F<S, P1<T>> modifyP1F(final F<A, P1<B>> f) {
return self.modifyP1F(f);
}
@Override
public <E> F<S, Validation<E, T>> modifyValidationF(final F<A, Validation<E, B>> f) {
return self.modifyValidationF(f);
}
@Override
public F<S, V2<T>> modifyV2F(final F<A, V2<B>> f) {
return self.modifyV2F(f);
}
@Override
public F<S, T> set(final B b) {
return self.set(b);
}
@Override
public Option<A> getOption(final S s) {
return self.getOption(s);
}
@Override
public F<S, T> modify(final F<A, B> f) {
return self.modify(f);
}
};
}
public static <S, T> PPrism<S, T, S, T> pId() {
return PIso.<S, T> pId().asPrism();
}
/** create a {@link PPrism} using the canonical functions: getOrModify and reverseGet */
public static <S, T, A, B> PPrism<S, T, A, B> pPrism(final F<S, Either<T, A>> getOrModify, final F<B, T> reverseGet) {
return new PPrism<S, T, A, B>() {
@Override
public Either<T, A> getOrModify(final S s) {
return getOrModify.f(s);
}
@Override
public T reverseGet(final B b) {
return reverseGet.f(b);
}
@Override
public Option<A> getOption(final S s) {
return getOrModify.f(s).right().toOption();
}
};
}
}