Skip to content

Commit 99acf00

Browse files
committed
Adding MergeHMaps, a Monoid merging HMaps by using key-specific semigroups
1 parent 74a58a0 commit 99acf00

File tree

3 files changed

+146
-0
lines changed

3 files changed

+146
-0
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
88
### Changed
99
- `HList#cons` static factory method auto-promotes to specialized `HList` if there is one
1010

11+
### Added
12+
- `MergeHMaps`, a `Monoid` that merges `HMap`s by merging the values via key-specified `Semigroup`s
13+
1114
## [5.1.0] - 2019-10-13
1215
### Changed
1316
- All monad transformers that can support composable parallelism do support it
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package com.jnape.palatable.lambda.monoid.builtin;
2+
3+
import com.jnape.palatable.lambda.adt.Maybe;
4+
import com.jnape.palatable.lambda.adt.hmap.HMap;
5+
import com.jnape.palatable.lambda.adt.hmap.TypeSafeKey;
6+
import com.jnape.palatable.lambda.functions.Fn1;
7+
import com.jnape.palatable.lambda.functions.Fn2;
8+
import com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft;
9+
import com.jnape.palatable.lambda.monoid.Monoid;
10+
import com.jnape.palatable.lambda.semigroup.Semigroup;
11+
12+
import java.util.Map;
13+
14+
import static com.jnape.palatable.lambda.adt.Maybe.just;
15+
import static com.jnape.palatable.lambda.adt.Maybe.maybe;
16+
import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map;
17+
import static com.jnape.palatable.lambda.monoid.builtin.Last.last;
18+
import static com.jnape.palatable.lambda.monoid.builtin.Present.present;
19+
import static com.jnape.palatable.lambda.optics.functions.Set.set;
20+
import static com.jnape.palatable.lambda.optics.lenses.MapLens.valueAt;
21+
import static java.util.Arrays.asList;
22+
import static java.util.Collections.emptyMap;
23+
24+
/**
25+
* A {@link Monoid} instance formed by merging {@link HMap HMaps} using the chosen
26+
* <code>{@link TypeSafeKey} -&gt; {@link Semigroup}</code>
27+
* {@link MergeHMaps#key(TypeSafeKey, Semigroup) mappings}, defaulting to {@link Last} in case no
28+
* {@link Semigroup} has been chosen for a given {@link TypeSafeKey}.
29+
*/
30+
public final class MergeHMaps implements Monoid<HMap> {
31+
32+
private final Map<TypeSafeKey<?, ?>, Fn2<HMap, HMap, HMap>> bindings;
33+
private final Φ<Fn2<HMap, HMap, HMap>> defaultBinding;
34+
35+
private MergeHMaps(Map<TypeSafeKey<?, ?>, Fn2<HMap, HMap, HMap>> bindings,
36+
Φ<Fn2<HMap, HMap, HMap>> defaultBinding) {
37+
this.bindings = bindings;
38+
this.defaultBinding = defaultBinding;
39+
}
40+
41+
public <A> MergeHMaps key(TypeSafeKey<?, A> key, Semigroup<A> semigroup) {
42+
return new MergeHMaps(set(valueAt(key), just(merge(key, present(semigroup))), bindings), defaultBinding);
43+
}
44+
45+
@Override
46+
public HMap identity() {
47+
return HMap.emptyHMap();
48+
}
49+
50+
@Override
51+
public HMap checkedApply(HMap x, HMap y) throws Throwable {
52+
return reduceLeft(asList(x, y));
53+
}
54+
55+
@Override
56+
public <B> HMap foldMap(Fn1<? super B, ? extends HMap> fn, Iterable<B> bs) {
57+
return FoldLeft.foldLeft((acc, m) -> FoldLeft.foldLeft((result, k) -> maybe(bindings.get(k))
58+
.orElseGet(() -> defaultBinding.eliminate(k))
59+
.apply(result, m), acc, m.keys()), identity(), map(fn, bs));
60+
}
61+
62+
public static MergeHMaps mergeHMaps() {
63+
return new MergeHMaps(emptyMap(), new Φ<Fn2<HMap, HMap, HMap>>() {
64+
@Override
65+
public <A> Fn2<HMap, HMap, HMap> eliminate(TypeSafeKey<?, A> key) {
66+
return merge(key, last());
67+
}
68+
});
69+
}
70+
71+
private static <A> Fn2<HMap, HMap, HMap> merge(TypeSafeKey<?, A> key, Semigroup<Maybe<A>> semigroup) {
72+
return (x, y) -> semigroup.apply(x.get(key), y.get(key))
73+
.fmap(a -> x.put(key, a))
74+
.orElse(x);
75+
}
76+
77+
@SuppressWarnings({"NonAsciiCharacters"})
78+
private interface Φ<R> {
79+
<A> R eliminate(TypeSafeKey<?, A> key);
80+
}
81+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.jnape.palatable.lambda.monoid.builtin;
2+
3+
import com.jnape.palatable.lambda.adt.hmap.TypeSafeKey;
4+
import org.junit.Test;
5+
6+
import static com.jnape.palatable.lambda.adt.hmap.HMap.emptyHMap;
7+
import static com.jnape.palatable.lambda.adt.hmap.HMap.hMap;
8+
import static com.jnape.palatable.lambda.adt.hmap.HMap.singletonHMap;
9+
import static com.jnape.palatable.lambda.adt.hmap.TypeSafeKey.typeSafeKey;
10+
import static com.jnape.palatable.lambda.monoid.builtin.Join.join;
11+
import static com.jnape.palatable.lambda.monoid.builtin.MergeHMaps.mergeHMaps;
12+
import static java.util.Arrays.asList;
13+
import static org.junit.Assert.assertEquals;
14+
15+
public class MergeHMapsTest {
16+
17+
@Test
18+
public void allKeysAccountedFor() {
19+
TypeSafeKey.Simple<String> stringKey = typeSafeKey();
20+
MergeHMaps mergeHMaps = mergeHMaps().key(stringKey, join());
21+
22+
assertEquals(emptyHMap(), mergeHMaps.apply(emptyHMap(), emptyHMap()));
23+
assertEquals(singletonHMap(stringKey, "foo"),
24+
mergeHMaps.apply(singletonHMap(stringKey, "foo"), emptyHMap()));
25+
assertEquals(singletonHMap(stringKey, "foobar"),
26+
mergeHMaps.apply(singletonHMap(stringKey, "foo"),
27+
singletonHMap(stringKey, "bar")));
28+
}
29+
30+
@Test
31+
public void unaccountedForKeyUsesLastByDefault() {
32+
TypeSafeKey.Simple<String> stringKey = typeSafeKey();
33+
34+
assertEquals(singletonHMap(stringKey, "foo"),
35+
mergeHMaps().apply(singletonHMap(stringKey, "foo"), emptyHMap()));
36+
assertEquals(singletonHMap(stringKey, "bar"),
37+
mergeHMaps().apply(emptyHMap(), singletonHMap(stringKey, "bar")));
38+
assertEquals(singletonHMap(stringKey, "bar"),
39+
mergeHMaps().apply(singletonHMap(stringKey, "foo"), singletonHMap(stringKey, "bar")));
40+
}
41+
42+
@Test
43+
public void sparseKeysAcrossMaps() {
44+
TypeSafeKey.Simple<String> stringKey = typeSafeKey();
45+
TypeSafeKey.Simple<Integer> intKey = typeSafeKey();
46+
TypeSafeKey.Simple<Boolean> boolKey = typeSafeKey();
47+
48+
MergeHMaps mergeHMaps = mergeHMaps()
49+
.key(stringKey, join())
50+
.key(intKey, Integer::sum);
51+
52+
assertEquals(hMap(stringKey, "foobar",
53+
intKey, 3,
54+
boolKey, false),
55+
mergeHMaps.reduceLeft(asList(singletonHMap(stringKey, "foo"),
56+
singletonHMap(intKey, 1),
57+
singletonHMap(boolKey, true),
58+
hMap(stringKey, "bar",
59+
intKey, 2,
60+
boolKey, false))));
61+
}
62+
}

0 commit comments

Comments
 (0)