Skip to content

Commit 9e16ed9

Browse files
committed
LambdaMap, lambda extension type for j.u.Map
1 parent e84c8e3 commit 9e16ed9

File tree

7 files changed

+154
-12
lines changed

7 files changed

+154
-12
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
1212
- `Product2-8` interfaces, representing general product types
1313
- `Union`, a semigroup that behaves like a lazy set union on `Iterable`s
1414
- `Difference`, a semigroup that behaves like a partially lazy set difference on `Iterable`s
15+
- `LambdaMap`, extension point for `j.u.Map`, similar to `LambdaIterable`
16+
- `Sequence#sequence` overloads for `j.u.Map` that traverse via intermediate `LambdaMap` instances
1517

1618
### Changed
1719
- `Tuple2-8` now implement `Product2-8`

src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
import com.jnape.palatable.lambda.functions.Fn2;
55
import com.jnape.palatable.lambda.functor.Applicative;
66
import com.jnape.palatable.lambda.traversable.LambdaIterable;
7+
import com.jnape.palatable.lambda.traversable.LambdaMap;
78
import com.jnape.palatable.lambda.traversable.Traversable;
89

10+
import java.util.Map;
911
import java.util.function.Function;
1012

1113
import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id;
@@ -83,4 +85,17 @@ TravApp extends Traversable<AppA, Trav>> AppTrav sequence(TravApp traversable,
8385
AppIterable sequence(IterableApp iterableApp, Function<Iterable<A>, ? extends AppIterable> pure) {
8486
return Sequence.<A, App, AppA, AppIterable, IterableApp>sequence(iterableApp).apply(pure);
8587
}
88+
89+
@SuppressWarnings({"unchecked", "RedundantTypeArguments"})
90+
public static <A, B, App extends Applicative, AppB extends Applicative<B, App>, AppMap extends Applicative<Map<A, B>, App>, MapApp extends Map<A, AppB>>
91+
Fn1<Function<Map<A, B>, ? extends AppMap>, AppMap> sequence(MapApp mapApp) {
92+
return pure -> (AppMap) Sequence.<B, App, LambdaMap<A, ?>, LambdaMap<A, B>, AppB, Applicative<LambdaMap<A, B>, App>, LambdaMap<A, AppB>>sequence(
93+
LambdaMap.wrap(mapApp), x -> pure.apply(x.unwrap()).fmap(LambdaMap::wrap))
94+
.fmap(LambdaMap::unwrap);
95+
}
96+
97+
public static <A, B, App extends Applicative, AppB extends Applicative<B, App>, AppMap extends Applicative<Map<A, B>, App>, MapApp extends Map<A, AppB>>
98+
AppMap sequence(MapApp mapApp, Function<Map<A, B>, ? extends AppMap> pure) {
99+
return Sequence.<A, B, App, AppB, AppMap, MapApp>sequence(mapApp).apply(pure);
100+
}
86101
}

src/main/java/com/jnape/palatable/lambda/monoid/Difference.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ public Iterable<A> identity() {
2222
return Collections::emptyIterator;
2323
}
2424

25-
2625
@Override
2726
public Iterable<A> apply(Iterable<A> xs, Iterable<A> ys) {
2827
return () -> {

src/main/java/com/jnape/palatable/lambda/traversable/LambdaIterable.java

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,17 @@
88
import java.util.function.Function;
99

1010
import static com.jnape.palatable.lambda.functions.builtin.fn1.Flatten.flatten;
11-
import static com.jnape.palatable.lambda.functions.builtin.fn2.CartesianProduct.cartesianProduct;
1211
import static com.jnape.palatable.lambda.functions.builtin.fn2.Cons.cons;
13-
import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into;
1412
import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map;
1513
import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldRight.foldRight;
1614
import static java.util.Collections.emptyList;
1715
import static java.util.Collections.singleton;
1816

1917
/**
20-
* Wrap an {@link Iterable} in a {@link Traversable} such that {@link Traversable#traverse(Function, Function)} applies
21-
* its computation against each element of the wrapped {@link Iterable}. Returns the result of <code>pure</code> if the
22-
* wrapped {@link Iterable} is empty.
18+
* Extension point for {@link Iterable} to adapt lambda core types like {@link Monad} and {@link Traversable}.
2319
*
24-
* @param <A> the Iterable element type
20+
* @param <A> the {@link Iterable} element type
21+
* @see LambdaMap
2522
*/
2623
public final class LambdaIterable<A> implements Monad<A, LambdaIterable>, Traversable<A, LambdaIterable> {
2724
private final Iterable<A> as;
@@ -34,7 +31,7 @@ private LambdaIterable(Iterable<? extends A> as) {
3431
/**
3532
* Unwrap the underlying {@link Iterable}.
3633
*
37-
* @return the wrapped Iterable
34+
* @return the wrapped {@link Iterable}
3835
*/
3936
public Iterable<A> unwrap() {
4037
return as;
@@ -61,10 +58,8 @@ public <B> LambdaIterable<B> pure(B b) {
6158
* @return the zipped LambdaIterable
6259
*/
6360
@Override
64-
@SuppressWarnings("Convert2MethodRef")
6561
public <B> LambdaIterable<B> zip(Applicative<Function<? super A, ? extends B>, LambdaIterable> appFn) {
66-
return wrap(map(into((f, x) -> f.apply(x)),
67-
cartesianProduct(appFn.<LambdaIterable<Function<? super A, ? extends B>>>coerce().unwrap(), as)));
62+
return Monad.super.zip(appFn).coerce();
6863
}
6964

7065
@Override
@@ -127,7 +122,7 @@ public static <A> LambdaIterable<A> wrap(Iterable<? extends A> as) {
127122
* Construct an empty {@link LambdaIterable} by wrapping {@link java.util.Collections#emptyList()}.
128123
*
129124
* @param <A> the Iterable element type
130-
* @return a {@link LambdaIterable} wrapping Collections.emptyList()
125+
* @return an empty {@link LambdaIterable}
131126
*/
132127
public static <A> LambdaIterable<A> empty() {
133128
return wrap(emptyList());
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package com.jnape.palatable.lambda.traversable;
2+
3+
import com.jnape.palatable.lambda.functions.Fn2;
4+
import com.jnape.palatable.lambda.functor.Applicative;
5+
import com.jnape.palatable.lambda.functor.Functor;
6+
7+
import java.util.Collections;
8+
import java.util.HashMap;
9+
import java.util.Map;
10+
import java.util.Objects;
11+
import java.util.function.Function;
12+
13+
import static com.jnape.palatable.lambda.adt.hlist.HList.tuple;
14+
import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into;
15+
import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map;
16+
import static com.jnape.palatable.lambda.functions.builtin.fn2.ToMap.toMap;
17+
import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft.foldLeft;
18+
import static java.util.Collections.emptyMap;
19+
20+
/**
21+
* Extension point for {@link Map} to adapt lambda core types like {@link Functor} and {@link Traversable}.
22+
*
23+
* @param <A> the {@link Map} element type
24+
* @see LambdaIterable
25+
*/
26+
public final class LambdaMap<A, B> implements Functor<B, LambdaMap<A, ?>>, Traversable<B, LambdaMap<A, ?>> {
27+
private final Map<A, B> map;
28+
29+
private LambdaMap(Map<A, B> map) {
30+
this.map = map;
31+
}
32+
33+
/**
34+
* Unwrap the underlying {@link Map}.
35+
*
36+
* @return the wrapped {@link Map}
37+
*/
38+
public Map<A, B> unwrap() {
39+
return map;
40+
}
41+
42+
@Override
43+
public <C> LambdaMap<A, C> fmap(Function<? super B, ? extends C> fn) {
44+
return wrap(toMap(HashMap::new, map(entry -> tuple(entry.getKey(), fn.apply(entry.getValue())), map.entrySet())));
45+
}
46+
47+
@Override
48+
@SuppressWarnings("unchecked")
49+
public <C, App extends Applicative, TravC extends Traversable<C, LambdaMap<A, ?>>, AppC extends Applicative<C, App>, AppTrav extends Applicative<TravC, App>> AppTrav traverse(
50+
Function<? super B, ? extends AppC> fn, Function<? super TravC, ? extends AppTrav> pure) {
51+
return foldLeft(Fn2.<AppTrav, Map.Entry<A, AppC>, AppTrav>fn2(appTrav -> into((k, appV) -> (AppTrav) appTrav.<TravC>zip(appV.fmap(v -> m -> {
52+
((LambdaMap<A, C>) m).unwrap().put(k, v);
53+
return (TravC) m;
54+
})))).toBiFunction(),
55+
pure.apply((TravC) LambdaMap.<A, C>wrap(new HashMap<>())),
56+
this.<AppC>fmap(fn).unwrap().entrySet());
57+
}
58+
59+
@Override
60+
public boolean equals(Object other) {
61+
return other instanceof LambdaMap && Objects.equals(map, ((LambdaMap) other).map);
62+
}
63+
64+
@Override
65+
public int hashCode() {
66+
return Objects.hash(map);
67+
}
68+
69+
@Override
70+
public String toString() {
71+
return "LambdaMap{map=" + map + '}';
72+
}
73+
74+
/**
75+
* Wrap a {@link Map} in a {@link LambdaMap}.
76+
*
77+
* @param map the {@link Map}
78+
* @param <A> the key type
79+
* @param <B> the value type
80+
* @return the {@link Map} wrapped in a {@link LambdaMap}
81+
*/
82+
public static <A, B> LambdaMap<A, B> wrap(Map<A, B> map) {
83+
return new LambdaMap<>(map);
84+
}
85+
86+
/**
87+
* Construct an empty {@link LambdaMap} by wrapping {@link Collections#emptyMap()}
88+
*
89+
* @param <A> the key type
90+
* @param <B> the value type
91+
* @return an empty {@link LambdaMap}
92+
*/
93+
public static <A, B> LambdaMap<A, B> empty() {
94+
return wrap(emptyMap());
95+
}
96+
}

src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SequenceTest.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id;
1414
import static com.jnape.palatable.lambda.functions.builtin.fn2.Sequence.sequence;
1515
import static java.util.Arrays.asList;
16+
import static java.util.Collections.singletonMap;
1617
import static org.junit.Assert.assertEquals;
1718
import static org.junit.Assert.assertThat;
1819
import static testsupport.matchers.IterableMatcher.iterates;
@@ -49,6 +50,12 @@ public void iterableSpecialization() {
4950
iterates(1, 2));
5051
}
5152

53+
@Test
54+
public void mapSpecialization() {
55+
assertEquals(right(singletonMap("foo", 1)),
56+
sequence(singletonMap("foo", right(1)), Either::right));
57+
}
58+
5259
@Test
5360
public void compilation() {
5461
Either<String, Maybe<Integer>> a = sequence(just(right(1)), Either::right);
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.jnape.palatable.lambda.traversable;
2+
3+
import com.jnape.palatable.traitor.annotations.TestTraits;
4+
import com.jnape.palatable.traitor.framework.Subjects;
5+
import com.jnape.palatable.traitor.runners.Traits;
6+
import org.junit.runner.RunWith;
7+
import testsupport.traits.FunctorLaws;
8+
import testsupport.traits.TraversableLaws;
9+
10+
import java.util.HashMap;
11+
12+
import static com.jnape.palatable.traitor.framework.Subjects.subjects;
13+
import static java.util.Collections.singletonMap;
14+
15+
@RunWith(Traits.class)
16+
public class LambdaMapTest {
17+
18+
@TestTraits({FunctorLaws.class, TraversableLaws.class})
19+
public Subjects<LambdaMap<Integer, String>> testSubject() {
20+
return subjects(LambdaMap.empty(),
21+
LambdaMap.wrap(singletonMap(1, "foo")),
22+
LambdaMap.wrap(new HashMap<Integer, String>() {{
23+
put(1, "foo");
24+
put(2, "bar");
25+
put(3, "baz");
26+
}}));
27+
}
28+
}

0 commit comments

Comments
 (0)