Skip to content

Commit e23bddd

Browse files
committed
Merge pull request #130 from javafp/optics
Optics (adapted from Scala Monocle library)
2 parents 35b55ff + 6650342 commit e23bddd

File tree

14 files changed

+3770
-0
lines changed

14 files changed

+3770
-0
lines changed
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
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 <A> Fold<A, A> id() {
110+
return PIso.<A, A> pId().asFold();
111+
}
112+
113+
public static final <A> Fold<Either<A, A>, A> codiagonal() {
114+
return new Fold<Either<A, A>, A>() {
115+
@Override
116+
public <B> F<Either<A, A>, B> foldMap(final Monoid<B> m, final F<A, B> f) {
117+
return e -> e.either(f, f);
118+
}
119+
};
120+
}
121+
122+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
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 <A> Getter<A, A> id() {
94+
return PIso.<A, A> pId().asGetter();
95+
}
96+
97+
public static final <A> Getter<Either<A, A>, A> codiagonal() {
98+
return getter(e -> e.either(Function.identity(), Function.identity()));
99+
}
100+
101+
public static final <S, A> Getter<S, A> getter(final F<S, A> get) {
102+
return new Getter<S, A>() {
103+
104+
@Override
105+
public A get(final S s) {
106+
return get.f(s);
107+
}
108+
};
109+
}
110+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package fj.data.optic;
2+
3+
import fj.F;
4+
import fj.P2;
5+
6+
/** {@link PIso} when S = T and A = B */
7+
public final class Iso<S, A> extends PIso<S, S, A, A> {
8+
9+
final PIso<S, S, A, A> pIso;
10+
11+
public Iso(final PIso<S, S, A, A> pIso) {
12+
this.pIso = pIso;
13+
}
14+
15+
@Override
16+
public A get(final S s) {
17+
return pIso.get(s);
18+
}
19+
20+
@Override
21+
public S reverseGet(final A a) {
22+
return pIso.reverseGet(a);
23+
}
24+
25+
@Override
26+
public Iso<A, S> reverse() {
27+
return new Iso<>(pIso.reverse());
28+
}
29+
30+
/** pair two disjoint {@link Iso} */
31+
public <S1, A1> Iso<P2<S, S1>, P2<A, A1>> product(final Iso<S1, A1> other) {
32+
return new Iso<>(pIso.product(other.pIso));
33+
}
34+
35+
@Override
36+
public <C> Iso<P2<S, C>, P2<A, C>> first() {
37+
return new Iso<>(pIso.first());
38+
}
39+
40+
@Override
41+
public <C> Iso<P2<C, S>, P2<C, A>> second() {
42+
return new Iso<>(pIso.second());
43+
}
44+
45+
/**********************************************************/
46+
/** Compose methods between an {@link Iso} and another Optics */
47+
/**********************************************************/
48+
49+
/** compose an {@link Iso} with a {@link Setter} */
50+
public final <C> Setter<S, C> composeSetter(final Setter<A, C> other) {
51+
return new Setter<>(pIso.composeSetter(other.pSetter));
52+
}
53+
54+
/** compose an {@link Iso} with a {@link Traversal} */
55+
public final <C> Traversal<S, C> composeTraversal(final Traversal<A, C> other) {
56+
return new Traversal<>(pIso.composeTraversal(other.pTraversal));
57+
}
58+
59+
/** compose an {@link Iso} with a {@link Optional} */
60+
public final <C> Optional<S, C> composeOptional(final Optional<A, C> other) {
61+
return new Optional<>(pIso.composeOptional(other.pOptional));
62+
}
63+
64+
/** compose an {@link Iso} with a {@link Prism} */
65+
public final <C> Prism<S, C> composePrism(final Prism<A, C> other) {
66+
return new Prism<>(pIso.composePrism(other.pPrism));
67+
}
68+
69+
/** compose an {@link Iso} with a {@link Lens} */
70+
public final <C> Lens<S, C> composeLens(final Lens<A, C> other) {
71+
return asLens().composeLens(other);
72+
}
73+
74+
/** compose an {@link Iso} with an {@link Iso} */
75+
public final <C> Iso<S, C> composeIso(final Iso<A, C> other) {
76+
return new Iso<>(pIso.composeIso(other.pIso));
77+
}
78+
79+
/****************************************************************/
80+
/** Transformation methods to view an {@link Iso} as another Optics */
81+
/****************************************************************/
82+
83+
/** view an {@link Iso} as a {@link Setter} */
84+
@Override
85+
public final Setter<S, A> asSetter() {
86+
return new Setter<>(pIso.asSetter());
87+
}
88+
89+
/** view an {@link Iso} as a {@link Traversal} */
90+
@Override
91+
public final Traversal<S, A> asTraversal() {
92+
return new Traversal<>(pIso.asTraversal());
93+
}
94+
95+
/** view an {@link Iso} as a {@link Optional} */
96+
@Override
97+
public final Optional<S, A> asOptional() {
98+
return new Optional<>(pIso.asOptional());
99+
}
100+
101+
/** view an {@link Iso} as a {@link Prism} */
102+
@Override
103+
public final Prism<S, A> asPrism() {
104+
return new Prism<>(pIso.asPrism());
105+
}
106+
107+
/** view an {@link Iso} as a {@link Lens} */
108+
@Override
109+
public final Lens<S, A> asLens() {
110+
return new Lens<>(pIso.asLens());
111+
}
112+
113+
/** create an {@link Iso} using a pair of functions: one to get the target and one to get the source. */
114+
public static <S, A> Iso<S, A> iso(final F<S, A> get, final F<A, S> reverseGet) {
115+
return new Iso<>(PIso.pIso(get, reverseGet));
116+
}
117+
118+
/**
119+
* create an {@link Iso} between any type and itself. id is the zero element of optics composition, for all optics o of type O
120+
* (e.g. Lens, Iso, Prism, ...):
121+
*
122+
* <pre>
123+
* o composeIso Iso.id == o
124+
* Iso.id composeO o == o
125+
* </pre>
126+
*
127+
* (replace composeO by composeLens, composeIso, composePrism, ...)
128+
*/
129+
public static <S> Iso<S, S> id() {
130+
return new Iso<>(PIso.pId());
131+
}
132+
133+
}

0 commit comments

Comments
 (0)