Skip to content

Commit 474e8a9

Browse files
committed
Adding converge for CoProduct3 and higher, as well as type-indexed projections
1 parent c9fd013 commit 474e8a9

File tree

9 files changed

+346
-38
lines changed

9 files changed

+346
-38
lines changed

src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct2.java

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,10 @@ public interface CoProduct2<A, B> extends Functor<B>, Bifunctor<A, B> {
3434
<R> R match(Function<? super A, ? extends R> aFn, Function<? super B, ? extends R> bFn);
3535

3636
/**
37-
* Diverge this coproduct by introducing another possible type that it could represent.
38-
* <p>
39-
* It's important to understand that this does not alter the essential value represented by this coproduct: if the
40-
* value was an <code>A</code> before divergence, it is still an <code>A</code>; likewise with <code>B</code>.
41-
* <p>
42-
* The purpose of this operation is to allow the use of a more convergent coproduct with a more divergent one; that
43-
* is, if a <code>CoProduct3&lt;String, Integer, Boolean&gt;</code> is expected, a <code>CoProduct2&lt;String,
44-
* Integer&gt;</code> should suffice.
37+
* Diverge this coproduct by introducing another possible type that it could represent. As no morphisms can be
38+
* provided mapping current types to the new type, this operation merely acts as a convenience method to allow the
39+
* use of a more convergent coproduct with a more divergent one; that is, if a <code>CoProduct3&lt;String, Integer,
40+
* Boolean&gt;</code> is expected, a <code>CoProduct2&lt;String, Integer&gt;</code> should suffice.
4541
* <p>
4642
* Generally, we use inheritance to make this a non-issue; however, with coproducts of differing magnitudes, we
4743
* cannot guarantee variance compatibility in one direction conveniently at construction time, and in the other
@@ -56,7 +52,7 @@ public interface CoProduct2<A, B> extends Functor<B>, Bifunctor<A, B> {
5652
* single magnitude difference.
5753
*
5854
* @param <C> the additional possible type of this coproduct
59-
* @return a Coproduct3&lt;A, B, C&gt;
55+
* @return a coproduct of the initial types plus the new type
6056
*/
6157
default <C> CoProduct3<A, B, C> diverge() {
6258
return match(CoProduct3::a, CoProduct3::b);
@@ -73,6 +69,26 @@ default Tuple2<Optional<A>, Optional<B>> project() {
7369
b -> tuple(Optional.empty(), Optional.of(b)));
7470
}
7571

72+
/**
73+
* Convenience method for projecting this coproduct onto a tuple and then extracting the first slot value.
74+
*
75+
* @return an optional value representing the projection of the "a" type index
76+
*/
77+
@SuppressWarnings("unused")
78+
default Optional<A> projectA() {
79+
return project()._1();
80+
}
81+
82+
/**
83+
* Convenience method for projecting this coproduct onto a tuple and then extracting the second slot value.
84+
*
85+
* @return an optional value representing the projection of the "b" type index
86+
*/
87+
@SuppressWarnings("unused")
88+
default Optional<B> projectB() {
89+
return project()._2();
90+
}
91+
7692
@Override
7793
default <C> CoProduct2<A, C> fmap(Function<? super B, ? extends C> fn) {
7894
return biMapR(fn);

src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct3.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,23 @@ default <D> CoProduct4<A, B, C, D> diverge() {
4545
return match(CoProduct4::a, CoProduct4::b, CoProduct4::c);
4646
}
4747

48+
/**
49+
* Converge this coproduct down to a lower order coproduct by mapping the last possible type into an earlier
50+
* possible type. This is the categorical dual of {@link CoProduct2#diverge}, which introduces the type
51+
* <code>C</code> and raises the order from 2 to 3.
52+
* <p>
53+
* The following laws hold for any two coproducts of single order difference:
54+
* <ul>
55+
* <li><em>Cancellation</em>: <code>coProductN.diverge().converge(CoProductN::a) == coProductN</code></li>
56+
* </ul>
57+
*
58+
* @param convergenceFn function from last possible type to earlier type
59+
* @return a coproduct of the initial types without the terminal type
60+
*/
61+
default CoProduct2<A, B> converge(Function<? super C, ? extends CoProduct2<A, B>> convergenceFn) {
62+
return match(CoProduct2::a, CoProduct2::b, convergenceFn);
63+
}
64+
4865
/**
4966
* Project this coproduct onto a tuple.
5067
*
@@ -57,6 +74,36 @@ default Tuple3<Optional<A>, Optional<B>, Optional<C>> project() {
5774
c -> tuple(Optional.empty(), Optional.empty(), Optional.of(c)));
5875
}
5976

77+
/**
78+
* Convenience method for projecting this coproduct onto a tuple and then extracting the first slot value.
79+
*
80+
* @return an optional value representing the projection of the "a" type index
81+
*/
82+
@SuppressWarnings("unused")
83+
default Optional<A> projectA() {
84+
return project()._1();
85+
}
86+
87+
/**
88+
* Convenience method for projecting this coproduct onto a tuple and then extracting the second slot value.
89+
*
90+
* @return an optional value representing the projection of the "b" type index
91+
*/
92+
@SuppressWarnings("unused")
93+
default Optional<B> projectB() {
94+
return project()._2();
95+
}
96+
97+
/**
98+
* Convenience method for projecting this coproduct onto a tuple and then extracting the third slot value.
99+
*
100+
* @return an optional value representing the projection of the "c" type index
101+
*/
102+
@SuppressWarnings("unused")
103+
default Optional<C> projectC() {
104+
return project()._3();
105+
}
106+
60107
@Override
61108
default <D> CoProduct3<A, B, D> fmap(Function<? super C, ? extends D> fn) {
62109
return biMapR(fn);

src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct4.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,18 @@ default <E> CoProduct5<A, B, C, D, E> diverge() {
4949
return match(CoProduct5::a, CoProduct5::b, CoProduct5::c, CoProduct5::d);
5050
}
5151

52+
/**
53+
* Converge this coproduct down to a lower order coproduct by mapping the last possible type into an earlier
54+
* possible type.
55+
*
56+
* @param convergenceFn function from last possible type to earlier type
57+
* @return a coproduct of the initial types without the terminal type
58+
* @see CoProduct3#converge
59+
*/
60+
default CoProduct3<A, B, C> converge(Function<? super D, ? extends CoProduct3<A, B, C>> convergenceFn) {
61+
return match(CoProduct3::a, CoProduct3::b, CoProduct3::c, convergenceFn);
62+
}
63+
5264
/**
5365
* Project this coproduct onto a tuple.
5466
*
@@ -62,6 +74,46 @@ default Tuple4<Optional<A>, Optional<B>, Optional<C>, Optional<D>> project() {
6274
d -> tuple(Optional.empty(), Optional.empty(), Optional.empty(), Optional.of(d)));
6375
}
6476

77+
/**
78+
* Convenience method for projecting this coproduct onto a tuple and then extracting the first slot value.
79+
*
80+
* @return an optional value representing the projection of the "a" type index
81+
*/
82+
@SuppressWarnings("unused")
83+
default Optional<A> projectA() {
84+
return project()._1();
85+
}
86+
87+
/**
88+
* Convenience method for projecting this coproduct onto a tuple and then extracting the second slot value.
89+
*
90+
* @return an optional value representing the projection of the "b" type index
91+
*/
92+
@SuppressWarnings("unused")
93+
default Optional<B> projectB() {
94+
return project()._2();
95+
}
96+
97+
/**
98+
* Convenience method for projecting this coproduct onto a tuple and then extracting the third slot value.
99+
*
100+
* @return an optional value representing the projection of the "c" type index
101+
*/
102+
@SuppressWarnings("unused")
103+
default Optional<C> projectC() {
104+
return project()._3();
105+
}
106+
107+
/**
108+
* Convenience method for projecting this coproduct onto a tuple and then extracting the fourth slot value.
109+
*
110+
* @return an optional value representing the projection of the "d" type index
111+
*/
112+
@SuppressWarnings("unused")
113+
default Optional<D> projectD() {
114+
return project()._4();
115+
}
116+
65117
@Override
66118
default <E> CoProduct4<A, B, C, E> fmap(Function<? super D, ? extends E> fn) {
67119
return biMapR(fn);

src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct5.java

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,17 @@ <R> R match(Function<? super A, ? extends R> aFn,
4242
Function<? super D, ? extends R> dFn,
4343
Function<? super E, ? extends R> eFn);
4444

45+
/**
46+
* Converge this coproduct down to a lower order coproduct by mapping the last possible type into an earlier
47+
* possible type.
48+
*
49+
* @param convergenceFn morphism <code>E -&gt; {@link CoProduct4}&lt;A, B, C, D&gt;</code>
50+
* @return a CoProduct4&lt;A, B, C, D&gt;
51+
*/
52+
default CoProduct4<A, B, C, D> converge(Function<? super E, ? extends CoProduct4<A, B, C, D>> convergenceFn) {
53+
return match(CoProduct4::a, CoProduct4::b, CoProduct4::c, CoProduct4::d, convergenceFn);
54+
}
55+
4556
/**
4657
* Project this coproduct onto a tuple.
4758
*
@@ -56,6 +67,56 @@ default Tuple5<Optional<A>, Optional<B>, Optional<C>, Optional<D>, Optional<E>>
5667
e -> tuple(Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.of(e)));
5768
}
5869

70+
/**
71+
* Convenience method for projecting this coproduct onto a tuple and then extracting the first slot value.
72+
*
73+
* @return an optional value representing the projection of the "a" type index
74+
*/
75+
@SuppressWarnings("unused")
76+
default Optional<A> projectA() {
77+
return project()._1();
78+
}
79+
80+
/**
81+
* Convenience method for projecting this coproduct onto a tuple and then extracting the second slot value.
82+
*
83+
* @return an optional value representing the projection of the "b" type index
84+
*/
85+
@SuppressWarnings("unused")
86+
default Optional<B> projectB() {
87+
return project()._2();
88+
}
89+
90+
/**
91+
* Convenience method for projecting this coproduct onto a tuple and then extracting the third slot value.
92+
*
93+
* @return an optional value representing the projection of the "c" type index
94+
*/
95+
@SuppressWarnings("unused")
96+
default Optional<C> projectC() {
97+
return project()._3();
98+
}
99+
100+
/**
101+
* Convenience method for projecting this coproduct onto a tuple and then extracting the fourth slot value.
102+
*
103+
* @return an optional value representing the projection of the "d" type index
104+
*/
105+
@SuppressWarnings("unused")
106+
default Optional<D> projectD() {
107+
return project()._4();
108+
}
109+
110+
/**
111+
* Convenience method for projecting this coproduct onto a tuple and then extracting the fifth slot value.
112+
*
113+
* @return an optional value representing the projection of the "e" type index
114+
*/
115+
@SuppressWarnings("unused")
116+
default Optional<E> projectE() {
117+
return project()._5();
118+
}
119+
59120
@Override
60121
default <F> CoProduct5<A, B, C, D, F> fmap(Function<? super E, ? extends F> fn) {
61122
return biMapR(fn);

src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct2Test.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
package com.jnape.palatable.lambda.adt.coproduct;
22

3+
import com.jnape.palatable.traitor.annotations.TestTraits;
4+
import com.jnape.palatable.traitor.runners.Traits;
35
import org.junit.Before;
46
import org.junit.Test;
5-
6-
import java.util.Optional;
7+
import org.junit.runner.RunWith;
8+
import testsupport.traits.CoProductProjections;
79

810
import static com.jnape.palatable.lambda.adt.coproduct.CoProduct2.a;
911
import static com.jnape.palatable.lambda.adt.coproduct.CoProduct2.b;
10-
import static com.jnape.palatable.lambda.adt.hlist.HList.tuple;
1112
import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id;
1213
import static org.junit.Assert.assertEquals;
1314

15+
@RunWith(Traits.class)
1416
public class CoProduct2Test {
1517

1618
private CoProduct2<Integer, Boolean> a;
@@ -34,10 +36,9 @@ public void diverge() {
3436
assertEquals(CoProduct3.b(true), b.diverge());
3537
}
3638

37-
@Test
38-
public void project() {
39-
assertEquals(tuple(Optional.of(1), Optional.empty()), CoProduct2.a(1).project());
40-
assertEquals(tuple(Optional.empty(), Optional.of("b")), CoProduct2.b("b").project());
39+
@TestTraits({CoProductProjections.class})
40+
public Class<?> projections() {
41+
return CoProduct2.class;
4142
}
4243

4344
@Test

src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct3Test.java

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,29 @@
11
package com.jnape.palatable.lambda.adt.coproduct;
22

3+
import com.jnape.palatable.traitor.annotations.TestTraits;
4+
import com.jnape.palatable.traitor.runners.Traits;
35
import org.junit.Before;
46
import org.junit.Test;
7+
import org.junit.runner.RunWith;
8+
import testsupport.traits.CoProductProjections;
59

6-
import java.util.Optional;
10+
import java.util.function.Function;
711

812
import static com.jnape.palatable.lambda.adt.coproduct.CoProduct3.a;
913
import static com.jnape.palatable.lambda.adt.coproduct.CoProduct3.b;
1014
import static com.jnape.palatable.lambda.adt.coproduct.CoProduct3.c;
11-
import static com.jnape.palatable.lambda.adt.hlist.HList.tuple;
1215
import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id;
1316
import static org.junit.Assert.assertEquals;
1417

18+
@RunWith(Traits.class)
1519
public class CoProduct3Test {
1620

1721
private CoProduct3<Integer, String, Boolean> a;
1822
private CoProduct3<Integer, String, Boolean> b;
1923
private CoProduct3<Integer, String, Boolean> c;
2024

2125
@Before
22-
public void setUp() throws Exception {
26+
public void setUp() {
2327
a = a(1);
2428
b = b("two");
2529
c = c(true);
@@ -40,10 +44,17 @@ public void diverge() {
4044
}
4145

4246
@Test
43-
public void project() {
44-
assertEquals(tuple(Optional.of(1), Optional.empty(), Optional.empty()), CoProduct3.a(1).project());
45-
assertEquals(tuple(Optional.empty(), Optional.of("b"), Optional.empty()), CoProduct3.b("b").project());
46-
assertEquals(tuple(Optional.empty(), Optional.empty(), Optional.of('c')), CoProduct3.c('c').project());
47+
public void converge() {
48+
Function<Boolean, CoProduct2<Integer, String>> convergenceFn = x -> x ? CoProduct2.a(1) : CoProduct2.b("false");
49+
assertEquals(CoProduct2.a(1), a.converge(convergenceFn));
50+
assertEquals(CoProduct2.b("two"), b.converge(convergenceFn));
51+
assertEquals(CoProduct2.a(1), CoProduct3.<Integer, String, Boolean>c(true).converge(convergenceFn));
52+
assertEquals(CoProduct2.b("false"), CoProduct3.<Integer, String, Boolean>c(false).converge(convergenceFn));
53+
}
54+
55+
@TestTraits({CoProductProjections.class})
56+
public Class<?> projections() {
57+
return CoProduct3.class;
4758
}
4859

4960
@Test

0 commit comments

Comments
 (0)