Skip to content

Commit 7269102

Browse files
committed
MapLens#valueAt and MapLens#asCopy receive overloads that take copyFn
1 parent ff424b0 commit 7269102

File tree

3 files changed

+83
-20
lines changed

3 files changed

+83
-20
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
99
- `CheckedEffect` is now a `CheckedFn1`
1010
- `CheckedSupplier` is now a `CheckedFn1`
1111
- `CheckedFn1` now overrides all possible methods with covariant return type
12+
- `MapLens#asCopy` has overload taking copy function
13+
- `MapLens#valueAt` has overload taking copy function
1214

1315
### Fixed
1416
- issue where certain ways to compose `Effect`s unintentionally nullified the effect

src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
import com.jnape.palatable.lambda.adt.Maybe;
44
import com.jnape.palatable.lambda.adt.hlist.Tuple2;
5+
import com.jnape.palatable.lambda.functions.Fn1;
56
import com.jnape.palatable.lambda.functions.builtin.fn2.Filter;
7+
import com.jnape.palatable.lambda.io.IO;
68
import com.jnape.palatable.lambda.lens.Iso;
79
import com.jnape.palatable.lambda.lens.Lens;
810

@@ -12,12 +14,15 @@
1214
import java.util.HashSet;
1315
import java.util.Map;
1416
import java.util.Set;
17+
import java.util.function.Function;
1518

1619
import static com.jnape.palatable.lambda.adt.Maybe.maybe;
20+
import static com.jnape.palatable.lambda.functions.builtin.fn2.Alter.alter;
1721
import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map;
1822
import static com.jnape.palatable.lambda.functions.builtin.fn2.ToCollection.toCollection;
1923
import static com.jnape.palatable.lambda.functions.builtin.fn2.ToMap.toMap;
2024
import static com.jnape.palatable.lambda.lens.Lens.Simple.adapt;
25+
import static com.jnape.palatable.lambda.lens.Lens.lens;
2126
import static com.jnape.palatable.lambda.lens.Lens.simpleLens;
2227
import static com.jnape.palatable.lambda.lens.functions.View.view;
2328
import static com.jnape.palatable.lambda.lens.lenses.MaybeLens.unLiftA;
@@ -31,6 +36,20 @@ public final class MapLens {
3136
private MapLens() {
3237
}
3338

39+
/**
40+
* A lens that focuses on a copy of a {@link Map} as a subtype <code>M</code>. Useful for composition to avoid
41+
* mutating a map reference.
42+
*
43+
* @param <M> the map subtype
44+
* @param <K> the key type
45+
* @param <V> the value type
46+
* @return a lens that focuses on copies of maps as a specific subtype
47+
*/
48+
public static <M extends Map<K, V>, K, V> Lens<Map<K, V>, M, M, M> asCopy(
49+
Function<? super Map<K, V>, ? extends M> copyFn) {
50+
return lens(copyFn, (__, copy) -> copy);
51+
}
52+
3453
/**
3554
* A lens that focuses on a copy of a Map. Useful for composition to avoid mutating a map reference.
3655
*
@@ -39,7 +58,26 @@ private MapLens() {
3958
* @return a lens that focuses on copies of maps
4059
*/
4160
public static <K, V> Lens.Simple<Map<K, V>, Map<K, V>> asCopy() {
42-
return simpleLens(HashMap::new, (__, copy) -> copy);
61+
return adapt(asCopy(HashMap::new));
62+
}
63+
64+
/**
65+
* A lens that focuses on a value at a key in a map, as a {@link Maybe}, and produces a subtype <code>M</code> on
66+
* the way back out.
67+
*
68+
* @param <M> the map subtype
69+
* @param <K> the key type
70+
* @param <V> the value type
71+
* @param k the key to focus on
72+
* @return a lens that focuses on the value at key, as a {@link Maybe}
73+
*/
74+
public static <M extends Map<K, V>, K, V> Lens<Map<K, V>, M, Maybe<V>, Maybe<V>> valueAt(
75+
Function<? super Map<K, V>, ? extends M> copyFn, K k) {
76+
return lens(m -> maybe(m.get(k)), (m, maybeV) -> maybeV
77+
.<Fn1<M, IO<M>>>fmap(v -> alter(updated -> updated.put(k, v)))
78+
.orElse(alter(updated -> updated.remove(k)))
79+
.apply(copyFn.apply(m))
80+
.unsafePerformIO());
4381
}
4482

4583
/**
@@ -51,16 +89,7 @@ public static <K, V> Lens.Simple<Map<K, V>, Map<K, V>> asCopy() {
5189
* @return a lens that focuses on the value at key, as a {@link Maybe}
5290
*/
5391
public static <K, V> Lens.Simple<Map<K, V>, Maybe<V>> valueAt(K k) {
54-
return simpleLens(m -> maybe(m.get(k)), (m, maybeV) -> {
55-
Map<K, V> updated = new HashMap<>(m);
56-
return maybeV.fmap(v -> {
57-
updated.put(k, v);
58-
return updated;
59-
}).orElseGet(() -> {
60-
updated.remove(k);
61-
return updated;
62-
});
63-
});
92+
return adapt(valueAt(HashMap::new, k));
6493
}
6594

6695
/**
@@ -75,7 +104,6 @@ public static <K, V> Lens.Simple<Map<K, V>, Maybe<V>> valueAt(K k) {
75104
* @param <V> the value type
76105
* @return a lens that focuses on the value at the key
77106
*/
78-
@SuppressWarnings("unchecked")
79107
public static <K, V> Lens.Simple<Map<K, V>, V> valueAt(K k, V defaultValue) {
80108
return adapt(unLiftB(unLiftA(valueAt(k), defaultValue)));
81109
}
@@ -150,8 +178,7 @@ public static <K, V> Lens.Simple<Map<K, V>, Map<V, K>> inverted() {
150178
/**
151179
* A lens that focuses on a map while mapping its values with the mapping {@link Iso}.
152180
* <p>
153-
* Note that for this lens to be lawful, <code>iso</code> must be bijective: that is, every <code>V</code> must
154-
* uniquely and invertibly map to exactly one <code>V2</code>.
181+
* Note that for this lens to be lawful, <code>iso</code> must be lawful.
155182
*
156183
* @param iso the mapping {@link Iso}
157184
* @param <K> the key type

src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.util.Collection;
77
import java.util.HashMap;
88
import java.util.HashSet;
9+
import java.util.LinkedHashMap;
910
import java.util.Map;
1011

1112
import static com.jnape.palatable.lambda.adt.Maybe.just;
@@ -15,7 +16,6 @@
1516
import static com.jnape.palatable.lambda.lens.functions.View.view;
1617
import static com.jnape.palatable.lambda.lens.lenses.MapLens.keys;
1718
import static com.jnape.palatable.lambda.lens.lenses.MapLens.mappingValues;
18-
import static com.jnape.palatable.lambda.lens.lenses.MapLens.valueAt;
1919
import static java.util.Arrays.asList;
2020
import static java.util.Collections.emptyMap;
2121
import static java.util.Collections.emptySet;
@@ -27,11 +27,12 @@
2727
import static org.junit.Assert.assertNotSame;
2828
import static org.junit.Assert.assertThat;
2929
import static testsupport.assertion.LensAssert.assertLensLawfulness;
30+
import static testsupport.matchers.IterableMatcher.iterates;
3031

3132
public class MapLensTest {
3233

3334
@Test
34-
public void asCopyFocusesOnMapThroughCopy() {
35+
public void asCopy() {
3536
assertLensLawfulness(MapLens.asCopy(),
3637
asList(emptyMap(), singletonMap("foo", 1), new HashMap<String, Integer>() {{
3738
put("foo", 1);
@@ -46,8 +47,29 @@ public void asCopyFocusesOnMapThroughCopy() {
4647
}
4748

4849
@Test
49-
public void valueAtFocusesOnValueAtKey() {
50-
assertLensLawfulness(valueAt("foo"),
50+
public void asCopyWithCopyFn() {
51+
assertLensLawfulness(MapLens.asCopy(LinkedHashMap::new),
52+
asList(emptyMap(), singletonMap("foo", 1), new HashMap<String, Integer>() {{
53+
put("foo", 1);
54+
put("bar", 2);
55+
put("baz", 3);
56+
}}),
57+
asList(emptyMap(), singletonMap("foo", 1), new HashMap<String, Integer>() {{
58+
put("foo", 1);
59+
put("bar", 2);
60+
put("baz", 3);
61+
}}));
62+
63+
assertThat(view(MapLens.asCopy(LinkedHashMap::new), new LinkedHashMap<String, Integer>() {{
64+
put("foo", 1);
65+
put("bar", 2);
66+
put("baz", 3);
67+
}}).keySet(), iterates("foo", "bar", "baz"));
68+
}
69+
70+
@Test
71+
public void valueAt() {
72+
assertLensLawfulness(MapLens.valueAt("foo"),
5173
asList(emptyMap(), singletonMap("foo", 1), new HashMap<String, Integer>() {{
5274
put("foo", 1);
5375
put("bar", 2);
@@ -57,8 +79,20 @@ public void valueAtFocusesOnValueAtKey() {
5779
}
5880

5981
@Test
60-
public void valueAtWithDefaultValueFocusedOnValueAtKey() {
61-
Lens.Simple<Map<String, Integer>, Integer> atFoo = valueAt("foo", -1);
82+
public void valueAtWithCopyFn() {
83+
assertLensLawfulness(MapLens.valueAt("foo"),
84+
asList(emptyMap(), singletonMap("foo", 1), new HashMap<String, Integer>() {{
85+
put("foo", 1);
86+
put("bar", 2);
87+
put("baz", 3);
88+
}}),
89+
asList(nothing(), just(1)));
90+
}
91+
92+
93+
@Test
94+
public void valueAtWithDefaultValue() {
95+
Lens.Simple<Map<String, Integer>, Integer> atFoo = MapLens.valueAt("foo", -1);
6296

6397
assertEquals((Integer) 1, view(atFoo, new HashMap<String, Integer>() {{
6498
put("foo", 1);

0 commit comments

Comments
 (0)