Skip to content

Commit e6cb9e8

Browse files
committed
Optics (adapted from Scala Monocle library)
1 parent 35b55ff commit e6cb9e8

14 files changed

Lines changed: 5007 additions & 0 deletions

File tree

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package fj.data.optic;
2+
3+
import fj.F;
4+
import fj.Function;
5+
import fj.Monoid;
6+
import fj.data.Either;
7+
import fj.data.List;
8+
import fj.data.Option;
9+
10+
/**
11+
* A {@link Fold} can be seen as a {@link Getter} with many targets or a weaker {@link PTraversal} which cannot modify its
12+
* target.
13+
*
14+
* {@link Fold} is on the top of the Optic hierarchy which means that {@link Getter}, {@link PTraversal}, {@link POptional},
15+
* {@link PLens}, {@link PPrism} and {@link PIso} are valid {@link Fold}
16+
*
17+
* @param <S> the source of a {@link Fold}
18+
* @param <A> the target of a {@link Fold}
19+
*/
20+
public abstract class Fold<S, A> {
21+
22+
/**
23+
* map each target to a {@link Monoid} and combine the results underlying representation of {@link Fold}, all {@link Fold}
24+
* methods are defined in terms of foldMap
25+
*/
26+
public abstract <M> F<S, M> foldMap(Monoid<M> m, F<A, M> f);
27+
28+
/** combine all targets using a target's {@link Monoid} */
29+
public final F<S, A> fold(final Monoid<A> m) {
30+
return foldMap(m, Function.identity());
31+
}
32+
33+
/**
34+
* get all the targets of a {@link Fold} TODO: Shall it return a Stream as there might be an infinite number of targets?
35+
*/
36+
public final List<A> getAll(final S s) {
37+
return foldMap(Monoid.listMonoid(), List::single).f(s);
38+
}
39+
40+
/** find the first target of a {@link Fold} matching the predicate */
41+
public final F<S, Option<A>> find(final F<A, Boolean> p) {
42+
return foldMap(Monoid.optionMonoid(), a -> p.f(a) ? Option.some(a) : Option.none());
43+
}
44+
45+
/** get the first target of a {@link Fold} */
46+
public final Option<A> headOption(final S s) {
47+
return find(__ -> true).f(s);
48+
}
49+
50+
/** check if at least one target satisfies the predicate */
51+
public final F<S, Boolean> exist(final F<A, Boolean> p) {
52+
return foldMap(Monoid.disjunctionMonoid, p);
53+
}
54+
55+
/** check if all targets satisfy the predicate */
56+
public final F<S, Boolean> all(final F<A, Boolean> p) {
57+
return foldMap(Monoid.conjunctionMonoid, p);
58+
}
59+
60+
/** join two {@link Fold} with the same target */
61+
public final <S1> Fold<Either<S, S1>, A> sum(final Fold<S1, A> other) {
62+
return new Fold<Either<S, S1>, A>() {
63+
@Override
64+
public <B> F<Either<S, S1>, B> foldMap(final Monoid<B> m, final F<A, B> f) {
65+
return s -> s.either(Fold.this.foldMap(m, f), other.foldMap(m, f));
66+
}
67+
};
68+
}
69+
70+
/**********************************************************/
71+
/** Compose methods between a {@link Fold} and another Optics */
72+
/**********************************************************/
73+
74+
/** compose a {@link Fold} with a {@link Fold} */
75+
public final <B> Fold<S, B> composeFold(final Fold<A, B> other) {
76+
return new Fold<S, B>() {
77+
@Override
78+
public <C> F<S, C> foldMap(final Monoid<C> m, final F<B, C> f) {
79+
return Fold.this.<C> foldMap(m, other.<C> foldMap(m, f));
80+
}
81+
};
82+
}
83+
84+
/** compose a {@link Fold} with a {@link Getter} */
85+
public final <C> Fold<S, C> composeGetter(final Getter<A, C> other) {
86+
return composeFold(other.asFold());
87+
}
88+
89+
/** compose a {@link Fold} with a {@link POptional} */
90+
public final <B, C, D> Fold<S, C> composeOptional(final POptional<A, B, C, D> other) {
91+
return composeFold(other.asFold());
92+
}
93+
94+
/** compose a {@link Fold} with a {@link PPrism} */
95+
public final <B, C, D> Fold<S, C> composePrism(final PPrism<A, B, C, D> other) {
96+
return composeFold(other.asFold());
97+
}
98+
99+
/** compose a {@link Fold} with a {@link PLens} */
100+
public final <B, C, D> Fold<S, C> composeLens(final PLens<A, B, C, D> other) {
101+
return composeFold(other.asFold());
102+
}
103+
104+
/** compose a {@link Fold} with a {@link PIso} */
105+
public final <B, C, D> Fold<S, C> composeIso(final PIso<A, B, C, D> other) {
106+
return composeFold(other.asFold());
107+
}
108+
109+
public static final <A> Fold<Either<A, A>, A> codiagonal() {
110+
return new Fold<Either<A, A>, A>() {
111+
@Override
112+
public <B> F<Either<A, A>, B> foldMap(final Monoid<B> m, final F<A, B> f) {
113+
return e -> e.either(f, f);
114+
}
115+
};
116+
}
117+
118+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package fj.data.optic;
2+
3+
import fj.F;
4+
import fj.Function;
5+
import fj.Monoid;
6+
import fj.P;
7+
import fj.P2;
8+
import fj.data.Either;
9+
10+
/**
11+
* A {@link Getter} can be seen as a glorified get method between a type S and a type A.
12+
*
13+
* A {@link Getter} is also a valid {@link Fold}
14+
*
15+
* @param <S> the source of a {@link Getter}
16+
* @param <A> the target of a {@link Getter}
17+
*/
18+
public abstract class Getter<S, A> {
19+
20+
Getter() {
21+
super();
22+
}
23+
24+
/** get the target of a {@link Getter} */
25+
public abstract A get(S s);
26+
27+
/** join two {@link Getter} with the same target */
28+
public final <S1> Getter<Either<S, S1>, A> sum(final Getter<S1, A> other) {
29+
return getter(e -> e.either(this::get, other::get));
30+
}
31+
32+
/** pair two disjoint {@link Getter} */
33+
public final <S1, A1> Getter<P2<S, S1>, P2<A, A1>> product(final Getter<S1, A1> other) {
34+
return getter(p2 -> P.p(this.get(p2._1()), other.get(p2._2())));
35+
}
36+
37+
public final <B> Getter<P2<S, B>, P2<A, B>> first() {
38+
return getter(p -> P.p(this.get(p._1()), p._2()));
39+
}
40+
41+
public final <B> Getter<P2<B, S>, P2<B, A>> second() {
42+
return getter(p -> P.p(p._1(), this.get(p._2())));
43+
}
44+
45+
/*************************************************************/
46+
/** Compose methods between a {@link Getter} and another Optics */
47+
/*************************************************************/
48+
49+
/** compose a {@link Getter} with a {@link Fold} */
50+
public final <B> Fold<S, B> composeFold(final Fold<A, B> other) {
51+
return asFold().composeFold(other);
52+
}
53+
54+
/** compose a {@link Getter} with a {@link Getter} */
55+
public final <B> Getter<S, B> composeGetter(final Getter<A, B> other) {
56+
return getter(s -> other.get(get(s)));
57+
}
58+
59+
/** compose a {@link Getter} with a {@link POptional} */
60+
public final <B, C, D> Fold<S, C> composeOptional(final POptional<A, B, C, D> other) {
61+
return asFold().composeOptional(other);
62+
}
63+
64+
/** compose a {@link Getter} with a {@link PPrism} */
65+
public final <B, C, D> Fold<S, C> composePrism(final PPrism<A, B, C, D> other) {
66+
return asFold().composePrism(other);
67+
}
68+
69+
/** compose a {@link Getter} with a {@link PLens} */
70+
public final <B, C, D> Getter<S, C> composeLens(final PLens<A, B, C, D> other) {
71+
return composeGetter(other.asGetter());
72+
}
73+
74+
/** compose a {@link Getter} with a {@link PIso} */
75+
public final <B, C, D> Getter<S, C> composeIso(final PIso<A, B, C, D> other) {
76+
return composeGetter(other.asGetter());
77+
}
78+
79+
/******************************************************************/
80+
/** Transformation methods to view a {@link Getter} as another Optics */
81+
/******************************************************************/
82+
83+
/** view a {@link Getter} with a {@link Fold} */
84+
public final Fold<S, A> asFold() {
85+
return new Fold<S, A>() {
86+
@Override
87+
public <B> F<S, B> foldMap(final Monoid<B> m, final F<A, B> f) {
88+
return s -> f.f(get(s));
89+
}
90+
};
91+
}
92+
93+
public static final <A> Getter<Either<A, A>, A> codiagonal() {
94+
return getter(e -> e.either(Function.identity(), Function.identity()));
95+
}
96+
97+
public static final <S, A> Getter<S, A> getter(final F<S, A> get) {
98+
return new Getter<S, A>() {
99+
100+
@Override
101+
public A get(final S s) {
102+
return get.f(s);
103+
}
104+
};
105+
}
106+
}

0 commit comments

Comments
 (0)