Skip to content

Commit 24a12ec

Browse files
committed
Adding default prisms for Either, Maybe, Maps, and UUIDs
1 parent 1820d9e commit 24a12ec

9 files changed

Lines changed: 342 additions & 0 deletions

File tree

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.jnape.palatable.lambda.optics.prisms;
2+
3+
import com.jnape.palatable.lambda.adt.Either;
4+
import com.jnape.palatable.lambda.adt.coproduct.CoProduct2;
5+
import com.jnape.palatable.lambda.optics.Prism;
6+
7+
import static com.jnape.palatable.lambda.optics.Prism.simplePrism;
8+
9+
/**
10+
* {@link Prism Prisms} for {@link Either}.
11+
*/
12+
public final class EitherPrism {
13+
14+
private EitherPrism() {
15+
}
16+
17+
/**
18+
* A {@link Prism} that focuses on the {@link Either#left(Object) left} value of an {@link Either}.
19+
*
20+
* @param <L> the left type
21+
* @param <R> the right type
22+
* @return the {@link Prism}
23+
*/
24+
public static <L, R> Prism.Simple<Either<L, R>, L> _left() {
25+
return simplePrism(CoProduct2::projectA, Either::left);
26+
}
27+
28+
/**
29+
* A {@link Prism} that focuses on the {@link Either#right(Object) right} value of an {@link Either}.
30+
*
31+
* @param <L> the left type
32+
* @param <R> the right type
33+
* @return the {@link Prism}
34+
*/
35+
public static <L, R> Prism.Simple<Either<L, R>, R> _right() {
36+
return simplePrism(CoProduct2::projectB, Either::right);
37+
}
38+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.jnape.palatable.lambda.optics.prisms;
2+
3+
import com.jnape.palatable.lambda.functions.Fn1;
4+
import com.jnape.palatable.lambda.optics.Prism;
5+
6+
import java.util.HashMap;
7+
import java.util.Map;
8+
9+
import static com.jnape.palatable.lambda.adt.Maybe.maybe;
10+
import static com.jnape.palatable.lambda.optics.Prism.Simple.adapt;
11+
import static com.jnape.palatable.lambda.optics.Prism.prism;
12+
import static java.util.Collections.singletonMap;
13+
14+
/**
15+
* {@link Prism Prisms} for {@link Map Maps}.
16+
*/
17+
public final class MapPrism {
18+
private MapPrism() {
19+
}
20+
21+
/**
22+
* A {@link Prism} that focuses on the value at a key in a {@link Map}, and produces an instance of <code>M</code>
23+
* on the way back out.
24+
*
25+
* @param copyFn the copy function
26+
* @param k the key to focus on
27+
* @param <M> the {@link Map} subtype
28+
* @param <K> the key type
29+
* @param <V> the value type
30+
* @return the {@link Prism}
31+
*/
32+
public static <M extends Map<K, V>, K, V> Prism<Map<K, V>, M, V, V> valueAt(Fn1<Map<K, V>, M> copyFn, K k) {
33+
return prism(m -> maybe(m.get(k)).toEither(copyFn.thunk(m)),
34+
v -> copyFn.apply(singletonMap(k, v)));
35+
}
36+
37+
/**
38+
* A {@link Prism} that focuses on the value at a key in a {@link Map} making no guarantees about the {@link Map}
39+
* interface.
40+
*
41+
* @param k the key to focus on
42+
* @param <K> the key type
43+
* @param <V> the value type
44+
* @return the {@link Prism}
45+
*/
46+
public static <K, V> Prism.Simple<Map<K, V>, V> valueAt(K k) {
47+
return adapt(valueAt(HashMap::new, k));
48+
}
49+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.jnape.palatable.lambda.optics.prisms;
2+
3+
import com.jnape.palatable.lambda.adt.Maybe;
4+
import com.jnape.palatable.lambda.adt.Unit;
5+
import com.jnape.palatable.lambda.adt.coproduct.CoProduct2;
6+
import com.jnape.palatable.lambda.optics.Prism;
7+
8+
import static com.jnape.palatable.lambda.adt.Maybe.nothing;
9+
import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly;
10+
import static com.jnape.palatable.lambda.optics.Prism.prism;
11+
import static com.jnape.palatable.lambda.optics.Prism.simplePrism;
12+
13+
/**
14+
* {@link Prism Prisms} for {@link Maybe}.
15+
*/
16+
public final class MaybePrism {
17+
18+
private MaybePrism() {
19+
}
20+
21+
/**
22+
* A {@link Prism} that focuses on present values in a {@link Maybe}.
23+
*
24+
* @param <A> {@link Maybe} the input value
25+
* @param <B> {@link Maybe} the output value
26+
* @return the {@link Prism}
27+
*/
28+
public static <A, B> Prism<Maybe<A>, Maybe<B>, A, B> _just() {
29+
return prism(maybeA -> maybeA.toEither(Maybe::nothing), Maybe::just);
30+
}
31+
32+
/**
33+
* A {@link Prism} that focuses on absent values in a {@link Maybe}, for symmetry.
34+
*
35+
* @param <A> {@link Maybe} the input and output value
36+
* @return the {@link Prism}
37+
*/
38+
public static <A> Prism.Simple<Maybe<A>, Unit> _nothing() {
39+
return simplePrism(CoProduct2::projectA, constantly(nothing()));
40+
}
41+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.jnape.palatable.lambda.optics.prisms;
2+
3+
import com.jnape.palatable.lambda.optics.Prism;
4+
5+
import java.util.UUID;
6+
7+
/**
8+
* {@link Prism Prisms} for {@link UUID}.
9+
*/
10+
public final class UUIDPrism {
11+
private UUIDPrism() {
12+
}
13+
14+
/**
15+
* A {@link Prism} that focuses on a {@link String} as a {@link UUID}.
16+
*
17+
* @return the {@link Prism}
18+
*/
19+
public static Prism.Simple<String, UUID> uuid() {
20+
return Prism.fromPartial(UUID::fromString, UUID::toString);
21+
}
22+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.jnape.palatable.lambda.optics.prisms;
2+
3+
import org.junit.Test;
4+
5+
import static com.jnape.palatable.lambda.adt.Either.left;
6+
import static com.jnape.palatable.lambda.adt.Either.right;
7+
import static java.util.Arrays.asList;
8+
import static java.util.Collections.singleton;
9+
import static testsupport.assertion.PrismAssert.assertPrismLawfulness;
10+
11+
public class EitherPrismTest {
12+
13+
@Test
14+
public void _right() {
15+
assertPrismLawfulness(EitherPrism._right(),
16+
asList(left("foo"), right(1)),
17+
singleton(1));
18+
}
19+
20+
@Test
21+
public void _left() {
22+
assertPrismLawfulness(EitherPrism._left(),
23+
asList(left("foo"), right(1)),
24+
singleton("foo"));
25+
}
26+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.jnape.palatable.lambda.optics.prisms;
2+
3+
import org.junit.Test;
4+
5+
import java.util.HashMap;
6+
import java.util.LinkedHashMap;
7+
8+
import static java.util.Arrays.asList;
9+
import static java.util.Collections.singleton;
10+
import static java.util.Collections.singletonMap;
11+
import static testsupport.assertion.PrismAssert.assertPrismLawfulness;
12+
13+
public class MapPrismTest {
14+
15+
@Test
16+
public void valueAtWithConstructor() {
17+
assertPrismLawfulness(MapPrism.valueAt(LinkedHashMap::new, "foo"),
18+
asList(new LinkedHashMap<>(),
19+
new LinkedHashMap<>(singletonMap("foo", 1)),
20+
new LinkedHashMap<>(singletonMap("bar", 2))),
21+
singleton(1));
22+
}
23+
24+
@Test
25+
public void valueAtWithoutConstructor() {
26+
assertPrismLawfulness(MapPrism.valueAt("foo"),
27+
asList(new HashMap<>(),
28+
new HashMap<>(singletonMap("foo", 1)),
29+
new HashMap<>(singletonMap("bar", 2))),
30+
singleton(1));
31+
}
32+
33+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.jnape.palatable.lambda.optics.prisms;
2+
3+
import org.junit.Test;
4+
5+
import static com.jnape.palatable.lambda.adt.Maybe.just;
6+
import static com.jnape.palatable.lambda.adt.Maybe.nothing;
7+
import static com.jnape.palatable.lambda.adt.Unit.UNIT;
8+
import static java.util.Arrays.asList;
9+
import static java.util.Collections.singleton;
10+
import static testsupport.assertion.PrismAssert.assertPrismLawfulness;
11+
12+
public class MaybePrismTest {
13+
14+
@Test
15+
public void _just() {
16+
assertPrismLawfulness(MaybePrism._just(),
17+
asList(just(1), nothing()),
18+
singleton(1));
19+
}
20+
21+
@Test
22+
public void _nothing() {
23+
assertPrismLawfulness(MaybePrism._nothing(),
24+
asList(just(1), nothing()),
25+
singleton(UNIT));
26+
}
27+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.jnape.palatable.lambda.optics.prisms;
2+
3+
import org.junit.Test;
4+
5+
import java.util.UUID;
6+
7+
import static java.util.Arrays.asList;
8+
import static java.util.Collections.singleton;
9+
import static testsupport.assertion.PrismAssert.assertPrismLawfulness;
10+
11+
public class UUIDPrismTest {
12+
13+
@Test
14+
public void uuid() {
15+
UUID uuid = UUID.randomUUID();
16+
assertPrismLawfulness(UUIDPrism.uuid(),
17+
asList("", "123", uuid.toString()),
18+
singleton(uuid));
19+
}
20+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package testsupport.assertion;
2+
3+
import com.jnape.palatable.lambda.adt.Maybe;
4+
import com.jnape.palatable.lambda.adt.hlist.Tuple2;
5+
import com.jnape.palatable.lambda.functions.Fn2;
6+
import com.jnape.palatable.lambda.functions.builtin.fn2.Map;
7+
import com.jnape.palatable.lambda.io.IO;
8+
import com.jnape.palatable.lambda.monoid.builtin.Present;
9+
import com.jnape.palatable.lambda.optics.Prism;
10+
11+
import java.util.Objects;
12+
13+
import static com.jnape.palatable.lambda.adt.Either.left;
14+
import static com.jnape.palatable.lambda.adt.Maybe.just;
15+
import static com.jnape.palatable.lambda.adt.Maybe.nothing;
16+
import static com.jnape.palatable.lambda.functions.Fn2.fn2;
17+
import static com.jnape.palatable.lambda.functions.builtin.fn1.CatMaybes.catMaybes;
18+
import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly;
19+
import static com.jnape.palatable.lambda.functions.builtin.fn2.CartesianProduct.cartesianProduct;
20+
import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into;
21+
import static com.jnape.palatable.lambda.functions.builtin.fn2.ReduceLeft.reduceLeft;
22+
import static com.jnape.palatable.lambda.optics.functions.Matching.matching;
23+
import static com.jnape.palatable.lambda.optics.functions.Pre.pre;
24+
import static com.jnape.palatable.lambda.optics.functions.Re.re;
25+
import static com.jnape.palatable.lambda.optics.functions.View.view;
26+
import static java.lang.String.format;
27+
import static java.lang.String.join;
28+
import static java.util.Arrays.asList;
29+
30+
public final class PrismAssert {
31+
32+
public static <S, A> void assertPrismLawfulness(Prism<S, S, A, A> prism,
33+
Iterable<S> ss,
34+
Iterable<A> bs) {
35+
Iterable<Tuple2<S, A>> cases = cartesianProduct(ss, bs);
36+
Present.<String>present((x, y) -> join("\n\n", x, y))
37+
.reduceLeft(asList(falsify("The result of a review can always be successfully previewed:",
38+
(s, b) -> view(pre(prism), view(re(prism), b)), (s, b) -> just(b), cases),
39+
falsify("If I can preview a value from an input, I can review the input to the value",
40+
(s, b) -> new PrismResult<>(view(pre(prism), s).fmap(constantly(s))),
41+
(s, b) -> new PrismResult<>(just(view(re(prism), b))), cases),
42+
falsify("A non-match result can always be converted back to an input",
43+
(s, b) -> new PrismResult<>(matching(prism, s).projectA().fmap(matching(prism))),
44+
(s, b) -> new PrismResult<>(just(left(s))), cases)))
45+
.peek(failures -> IO.throwing(new AssertionError("Lens law failures\n\n" + failures)));
46+
}
47+
48+
private static <S, A, X> Maybe<String> falsify(String label, Fn2<S, A, X> l, Fn2<S, A, X> r,
49+
Iterable<Tuple2<S, A>> cases) {
50+
return Map.<Tuple2<S, A>, Maybe<String>>map(into((s, b) -> {
51+
X x = l.apply(s, b);
52+
X y = r.apply(s, b);
53+
return Objects.equals(x, y) ? nothing() : just(format("S <%s>, B <%s> (%s != %s)", s, b, x, y));
54+
}))
55+
.fmap(catMaybes())
56+
.fmap(reduceLeft((x, y) -> x + "\n\t - " + y))
57+
.fmap(maybeFailures -> maybeFailures.fmap(failures -> "\"" + label + "\" failed for the following cases:\n\n\t - " + failures))
58+
.apply(cases);
59+
}
60+
61+
private static final class PrismResult<S> {
62+
private final Maybe<S> maybeS;
63+
64+
private PrismResult(Maybe<S> maybeS) {
65+
this.maybeS = maybeS;
66+
}
67+
68+
@Override
69+
public boolean equals(Object other) {
70+
if (other instanceof PrismResult) {
71+
return maybeS.zip(((PrismResult<?>) other).maybeS.fmap(fn2(Objects::equals))).orElse(true);
72+
}
73+
return false;
74+
}
75+
76+
@Override
77+
public int hashCode() {
78+
return Objects.hash(maybeS);
79+
}
80+
81+
@Override
82+
public String toString() {
83+
return maybeS.toString();
84+
}
85+
}
86+
}

0 commit comments

Comments
 (0)