Skip to content

Commit 65b0e30

Browse files
committed
Composing a prism with another prism yields a prism. Defaults for optics
1 parent 9e238bf commit 65b0e30

4 files changed

Lines changed: 208 additions & 6 deletions

File tree

src/main/java/com/jnape/palatable/lambda/optics/Prism.java

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,54 @@ public interface Prism<S, T, A, B> extends
8484
return optic.apply(pafb);
8585
}
8686

87+
/**
88+
* {@inheritDoc}
89+
*/
90+
@Override
91+
default <Z, C> Prism<S, T, Z, C> andThen(ProtoOptic<? super Cocartesian<?, ?, ?>, A, B, Z, C> f) {
92+
return prism(ProtoOptic.super.andThen(f));
93+
}
94+
95+
/**
96+
* {@inheritDoc}
97+
*/
98+
@Override
99+
default <R, U> Prism<R, U, A, B> compose(ProtoOptic<? super Cocartesian<?, ?, ?>, R, U, S, T> g) {
100+
return prism(ProtoOptic.super.compose(g));
101+
}
102+
103+
/**
104+
* {@inheritDoc}
105+
*/
106+
@Override
107+
default <R> Prism<R, T, A, B> mapS(Fn1<? super R, ? extends S> fn) {
108+
return prism(ProtoOptic.super.mapS(fn));
109+
}
110+
111+
/**
112+
* {@inheritDoc}
113+
*/
114+
@Override
115+
default <U> Prism<S, U, A, B> mapT(Fn1<? super T, ? extends U> fn) {
116+
return prism(ProtoOptic.super.mapT(fn));
117+
}
118+
119+
/**
120+
* {@inheritDoc}
121+
*/
122+
@Override
123+
default <C> Prism<S, T, C, B> mapA(Fn1<? super A, ? extends C> fn) {
124+
return prism(ProtoOptic.super.mapA(fn));
125+
}
126+
127+
/**
128+
* {@inheritDoc}
129+
*/
130+
@Override
131+
default <Z> Prism<S, T, A, Z> mapB(Fn1<? super Z, ? extends B> fn) {
132+
return prism(ProtoOptic.super.mapB(fn));
133+
}
134+
87135
/**
88136
* {@inheritDoc}
89137
*/
@@ -264,10 +312,21 @@ static <S, A> Prism.Simple<S, A> simplePrism(Fn1<? super S, ? extends Maybe<A>>
264312
return Prism.<S, S, A, A>prism(s -> sMaybeA.apply(s).toEither(() -> s), as)::toOptic;
265313
}
266314

267-
268-
static <S, A> Prism.Simple<S, A> fromPartial(Fn1<? super S, ? extends A> partialSa,
269-
Fn1<? super A, ? extends S> as) {
270-
return adapt(prism(partialSa.<S, A>diMap(downcast(), upcast()).choose(), as));
315+
/**
316+
* Static factory method for creating a {@link Prism} from a partial function <code>S -&gt; A</code> and a total
317+
* function <code>B -&gt; S</code>.
318+
*
319+
* @param partialSa the partial direction
320+
* @param bs the reverse total direction
321+
* @param <S> the input that might fail to map to its output and the guaranteed output from the other
322+
* direction
323+
* @param <A> the output that might fail to be produced
324+
* @param <B> the input that guarantees its output in the other direction
325+
* @return the {@link Prism}
326+
*/
327+
static <S, A, B> Prism<S, S, A, B> fromPartial(Fn1<? super S, ? extends A> partialSa,
328+
Fn1<? super B, ? extends S> bs) {
329+
return prism(partialSa.<S, A>diMap(downcast(), upcast()).choose(), bs);
271330
}
272331

273332
/**
@@ -322,5 +381,22 @@ static <S, A> Prism.Simple<S, A> adapt(
322381
Optic<? super Cocartesian<?, ?, ?>, ? super Functor<?, ?>, S, S, A, A> optic) {
323382
return adapt(prism(optic));
324383
}
384+
385+
/**
386+
* Static factory method for creating a {@link Prism.Simple simple Prism} from a partial function
387+
* <code>S -&gt; A</code> and a total function <code>A -&gt; T</code>.
388+
*
389+
* @param partialSa the partial direction
390+
* @param as the reverse total direction
391+
* @param <S> the input that might fail to map to its output and the guaranteed output from the other
392+
* direction
393+
* @param <A> the output that might fail to be produced and the input that guarantees its output in the
394+
* other direction
395+
* @return the {@link Prism.Simple simple Prism}
396+
*/
397+
static <S, A> Prism.Simple<S, A> fromPartial(Fn1<? super S, ? extends A> partialSa,
398+
Fn1<? super A, ? extends S> as) {
399+
return adapt(Prism.fromPartial(partialSa, as));
400+
}
325401
}
326402
}

src/main/java/com/jnape/palatable/lambda/optics/ProtoOptic.java

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
package com.jnape.palatable.lambda.optics;
22

3+
import com.jnape.palatable.lambda.functions.Fn1;
34
import com.jnape.palatable.lambda.functions.specialized.Pure;
45
import com.jnape.palatable.lambda.functor.Applicative;
56
import com.jnape.palatable.lambda.functor.Functor;
67
import com.jnape.palatable.lambda.functor.Profunctor;
78
import com.jnape.palatable.lambda.functor.builtin.Identity;
89

10+
import static com.jnape.palatable.lambda.optics.Optic.reframe;
11+
912
/**
1013
* A generic supertype representation for a profunctor {@link Optic} that requires a {@link Pure} implementation to
11-
* derive its {@link Functor} constraint and graduate to a full-fledge {@link Optic}.
14+
* derive its {@link Functor} constraint and graduate to a full-fledge {@link Optic}, equipped with a default
15+
* {@link Optic} instance for the profunctor constraint and {@link Identity}.
1216
*
1317
* @param <P> the {@link Profunctor} bound
1418
* @param <S> the left side of the output profunctor
@@ -30,11 +34,102 @@ public interface ProtoOptic<P extends Profunctor<?, ?, ? extends P>, S, T, A, B>
3034
*/
3135
<F extends Applicative<?, F>> Optic<P, F, S, T, A, B> toOptic(Pure<F> pure);
3236

37+
/**
38+
* {@inheritDoc}
39+
*/
3340
@Override
3441
default <CoP extends Profunctor<?, ?, ? extends P>, CoF extends Functor<?, ? extends Identity<?>>,
3542
FB extends Functor<B, ? extends CoF>, FT extends Functor<T, ? extends CoF>,
3643
PAFB extends Profunctor<A, FB, ? extends CoP>,
3744
PSFT extends Profunctor<S, FT, ? extends CoP>> PSFT apply(PAFB pafb) {
3845
return toOptic(Pure.<Identity<?>>pure(Identity::new)).apply(pafb);
3946
}
47+
48+
/**
49+
* Left-to-right composition of proto-optics. Requires compatibility between <code>S</code> and <code>T</code>.
50+
*
51+
* @param f the other proto-optic
52+
* @param <Z> the new left side of the input profunctor
53+
* @param <C> the new right side's functor embedding of the input profunctor
54+
* @return the composed proto-optic
55+
*/
56+
default <Z, C> ProtoOptic<P, S, T, Z, C> andThen(ProtoOptic<? super P, A, B, Z, C> f) {
57+
return new ProtoOptic<P, S, T, Z, C>() {
58+
@Override
59+
public <F extends Applicative<?, F>> Optic<P, F, S, T, Z, C> toOptic(Pure<F> pure) {
60+
Optic<? super P, F, A, B, Z, C> optic = f.toOptic(pure);
61+
return ProtoOptic.this.toOptic(pure).andThen(reframe(optic));
62+
}
63+
};
64+
}
65+
66+
/**
67+
* Right-to-Left composition of proto-optics. Requires compatibility between <code>A</code> and <code>B</code>.
68+
*
69+
* @param g the other proto-optic
70+
* @param <R> the new left side of the output profunctor
71+
* @param <U> the new right side's functor embedding of the output profunctor
72+
* @return the composed proto-optic
73+
*/
74+
default <R, U> ProtoOptic<P, R, U, A, B> compose(ProtoOptic<? super P, R, U, S, T> g) {
75+
return new ProtoOptic<P, R, U, A, B>() {
76+
@Override
77+
public <F extends Applicative<?, F>> Optic<P, F, R, U, A, B> toOptic(Pure<F> pure) {
78+
Optic<? super P, F, R, U, S, T> optic = g.toOptic(pure);
79+
return ProtoOptic.this.toOptic(pure).compose(reframe(optic));
80+
}
81+
};
82+
}
83+
84+
/**
85+
* {@inheritDoc}
86+
*/
87+
@Override
88+
default <R> ProtoOptic<P, R, T, A, B> mapS(Fn1<? super R, ? extends S> fn) {
89+
return new ProtoOptic<P, R, T, A, B>() {
90+
@Override
91+
public <F extends Applicative<?, F>> Optic<P, F, R, T, A, B> toOptic(Pure<F> pure) {
92+
return ProtoOptic.this.toOptic(pure).mapS(fn);
93+
}
94+
};
95+
}
96+
97+
/**
98+
* {@inheritDoc}
99+
*/
100+
@Override
101+
default <U> ProtoOptic<P, S, U, A, B> mapT(Fn1<? super T, ? extends U> fn) {
102+
return new ProtoOptic<P, S, U, A, B>() {
103+
@Override
104+
public <F extends Applicative<?, F>> Optic<P, F, S, U, A, B> toOptic(Pure<F> pure) {
105+
return ProtoOptic.this.toOptic(pure).mapT(fn);
106+
}
107+
};
108+
}
109+
110+
/**
111+
* {@inheritDoc}
112+
*/
113+
@Override
114+
default <C> ProtoOptic<P, S, T, C, B> mapA(Fn1<? super A, ? extends C> fn) {
115+
return new ProtoOptic<P, S, T, C, B>() {
116+
@Override
117+
public <F extends Applicative<?, F>> Optic<P, F, S, T, C, B> toOptic(Pure<F> pure) {
118+
return ProtoOptic.this.toOptic(pure).mapA(fn);
119+
}
120+
};
121+
}
122+
123+
/**
124+
* {@inheritDoc}
125+
*/
126+
@Override
127+
default <Z> ProtoOptic<P, S, T, A, Z> mapB(Fn1<? super Z, ? extends B> fn) {
128+
return new ProtoOptic<P, S, T, A, Z>() {
129+
@Override
130+
public <F extends Applicative<?, F>> Optic<P, F, S, T, A, Z> toOptic(Pure<F> pure) {
131+
return ProtoOptic.this.toOptic(pure).mapB(fn);
132+
}
133+
};
134+
}
40135
}

src/main/java/com/jnape/palatable/lambda/optics/prisms/UUIDPrism.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@
44

55
import java.util.UUID;
66

7+
import static com.jnape.palatable.lambda.optics.Prism.Simple.fromPartial;
8+
79
/**
810
* {@link Prism Prisms} for {@link UUID}.
911
*/
1012
public final class UUIDPrism {
13+
1114
private UUIDPrism() {
1215
}
1316

@@ -17,6 +20,6 @@ private UUIDPrism() {
1720
* @return the {@link Prism}
1821
*/
1922
public static Prism.Simple<String, UUID> uuid() {
20-
return Prism.fromPartial(UUID::fromString, UUID::toString);
23+
return fromPartial(UUID::fromString, UUID::toString);
2124
}
2225
}

src/test/java/com/jnape/palatable/lambda/optics/PrismTest.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,17 @@
1515
import static com.jnape.palatable.lambda.adt.Either.left;
1616
import static com.jnape.palatable.lambda.adt.Either.right;
1717
import static com.jnape.palatable.lambda.adt.Maybe.just;
18+
import static com.jnape.palatable.lambda.functions.builtin.fn2.Eq.eq;
1819
import static com.jnape.palatable.lambda.optics.Prism.prism;
1920
import static com.jnape.palatable.lambda.optics.Prism.simplePrism;
2021
import static com.jnape.palatable.lambda.optics.functions.Matching.matching;
2122
import static com.jnape.palatable.lambda.optics.functions.Pre.pre;
2223
import static com.jnape.palatable.lambda.optics.functions.Re.re;
2324
import static com.jnape.palatable.lambda.optics.functions.View.view;
25+
import static java.util.Collections.emptyList;
26+
import static java.util.Collections.singletonList;
2427
import static org.junit.Assert.assertEquals;
28+
import static testsupport.assertion.PrismAssert.assertPrismLawfulness;
2529

2630
@RunWith(Traits.class)
2731
public class PrismTest {
@@ -60,4 +64,28 @@ public void unPrismExtractsMappings() {
6064
assertEquals(right(123), sis.apply("123"));
6165
assertEquals(left("foo"), sis.apply("foo"));
6266
}
67+
68+
@Test
69+
public void andThen() {
70+
Prism.Simple<String, Float> stringFloat = Prism.Simple.fromPartial(Float::parseFloat, Object::toString);
71+
Prism.Simple<Float, Integer> floatInt = simplePrism(f -> just(f.intValue()).filter(i -> eq(i.floatValue(), f)),
72+
Integer::floatValue);
73+
Prism<String, String, Integer, Integer> composed = stringFloat.andThen(floatInt);
74+
75+
assertPrismLawfulness(composed, singletonList("1.2"), singletonList(1));
76+
assertPrismLawfulness(composed, singletonList("1.0"), singletonList(1));
77+
assertPrismLawfulness(composed, singletonList("foo"), emptyList());
78+
}
79+
80+
@Test
81+
public void composed() {
82+
Prism.Simple<String, Float> stringFloat = Prism.Simple.fromPartial(Float::parseFloat, Object::toString);
83+
Prism.Simple<Float, Integer> floatInt = simplePrism(f -> just(f.intValue()).filter(i -> eq(i.floatValue(), f)),
84+
Integer::floatValue);
85+
Prism<String, String, Integer, Integer> composed = floatInt.compose(stringFloat);
86+
87+
assertPrismLawfulness(composed, singletonList("1.2"), singletonList(1));
88+
assertPrismLawfulness(composed, singletonList("1.0"), singletonList(1));
89+
assertPrismLawfulness(composed, singletonList("foo"), emptyList());
90+
}
6391
}

0 commit comments

Comments
 (0)