From a3dcb9ba988ea8035be3767821f86aae9b522e8a Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 10 Jul 2016 00:15:14 -0500 Subject: [PATCH 01/29] Initial lens support with Const and Identity functor to support view/set/over --- .../lambda/functor/builtin/Const.java | 42 ++++++++++++++++ .../lambda/functor/builtin/Identity.java | 32 ++++++++++++ .../com/jnape/palatable/lambda/lens/Lens.java | 40 +++++++++++++++ .../palatable/lambda/lens/functions/Over.java | 38 ++++++++++++++ .../palatable/lambda/lens/functions/Set.java | 38 ++++++++++++++ .../palatable/lambda/lens/functions/View.java | 29 +++++++++++ .../lambda/functor/builtin/ConstTest.java | 18 +++++++ .../lambda/functor/builtin/IdentityTest.java | 13 +++++ .../jnape/palatable/lambda/lens/LensTest.java | 50 +++++++++++++++++++ .../lambda/lens/functions/OverTest.java | 22 ++++++++ .../lambda/lens/functions/SetTest.java | 21 ++++++++ .../lambda/lens/functions/ViewTest.java | 22 ++++++++ 12 files changed, 365 insertions(+) create mode 100644 src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java create mode 100644 src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java create mode 100644 src/main/java/com/jnape/palatable/lambda/lens/Lens.java create mode 100644 src/main/java/com/jnape/palatable/lambda/lens/functions/Over.java create mode 100644 src/main/java/com/jnape/palatable/lambda/lens/functions/Set.java create mode 100644 src/main/java/com/jnape/palatable/lambda/lens/functions/View.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functor/builtin/ConstTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functor/builtin/IdentityTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/lens/LensTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/lens/functions/OverTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/lens/functions/SetTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/lens/functions/ViewTest.java diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java new file mode 100644 index 000000000..e04739e46 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java @@ -0,0 +1,42 @@ +package com.jnape.palatable.lambda.functor.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.functor.Functor; + +public final class Const implements Functor, Bifunctor { + + private final A a; + + public Const(A a) { + this.a = a; + } + + public A runConst() { + return a; + } + + @Override + @SuppressWarnings("unchecked") + public Const fmap(Fn1 fn) { + return (Const) this; + } + + @Override + @SuppressWarnings("unchecked") + public Const biMapL(Fn1 fn) { + return (Const) Bifunctor.super.biMapL(fn); + } + + @Override + @SuppressWarnings("unchecked") + public Const biMapR(Fn1 fn) { + return (Const) Bifunctor.super.biMapR(fn); + } + + @Override + public Const biMap(Fn1 lFn, + Fn1 rFn) { + return new Const<>(lFn.apply(a)); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java new file mode 100644 index 000000000..c0e2db683 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java @@ -0,0 +1,32 @@ +package com.jnape.palatable.lambda.functor.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.Functor; + +public class Identity implements Functor { + + private final A a; + + public Identity(A a) { + this.a = a; + } + + public A runIdentity() { + return a; + } + + @Override + public Identity fmap(Fn1 fn) { + return new Identity<>(fn.apply(a)); + } + + @Override + public boolean equals(Object other) { + return other instanceof Identity && a.equals(((Identity) other).a); + } + + @Override + public int hashCode() { + return a.hashCode(); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java new file mode 100644 index 000000000..c015b5cce --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java @@ -0,0 +1,40 @@ +package com.jnape.palatable.lambda.lens; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functor.Functor; + +@FunctionalInterface +public interface Lens { + + , FB extends Functor> FT apply(Fn1 fn, S s); + + default , FB extends Functor> Fixed fix() { + return this::apply; + } + + static Lens lens(Fn1 getter, + Fn2 setter) { + return new Lens() { + @Override + @SuppressWarnings("unchecked") + public , FB extends Functor> FT apply(Fn1 fn, + S s) { + return (FT) fn.apply(getter.apply(s)).fmap(setter.apply(s)); + } + }; + } + + @SuppressWarnings("unchecked") + static Lens.Simple simpleLens(Fn1 getter, + Fn2 setter) { + return lens(getter, setter)::apply; + } + + interface Simple extends Lens { + } + + interface Fixed, FB extends Functor> + extends Fn2, S, FT> { + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/lens/functions/Over.java b/src/main/java/com/jnape/palatable/lambda/lens/functions/Over.java new file mode 100644 index 000000000..4ee24be65 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/lens/functions/Over.java @@ -0,0 +1,38 @@ +package com.jnape.palatable.lambda.lens.functions; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functor.builtin.Identity; +import com.jnape.palatable.lambda.lens.Lens; + +public final class Over implements Fn3, Fn1, S, T> { + + private Over() { + } + + @Override + public T apply(Lens lens, Fn1 fn, S s) { + return lens., Identity>fix() + .apply(fn.fmap((Fn1>) Identity::new), s) + .runIdentity(); + } + + public static Over over() { + return new Over<>(); + } + + public static Fn2, S, T> over( + Lens lens) { + return Over.over().apply(lens); + } + + public static Fn1 over(Lens lens, + Fn1 fn) { + return over(lens).apply(fn); + } + + public static T over(Lens lens, Fn1 fn, S s) { + return over(lens, fn).apply(s); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/lens/functions/Set.java b/src/main/java/com/jnape/palatable/lambda/lens/functions/Set.java new file mode 100644 index 000000000..ad2df38ac --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/lens/functions/Set.java @@ -0,0 +1,38 @@ +package com.jnape.palatable.lambda.lens.functions; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functor.builtin.Identity; +import com.jnape.palatable.lambda.lens.Lens; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; + +public final class Set implements Fn3, B, S, T> { + + private Set() { + } + + @Override + public T apply(Lens lens, B b, S s) { + return lens., Identity>fix() + .apply(constantly(b).fmap(Identity::new), s) + .runIdentity(); + } + + public static Set set() { + return new Set<>(); + } + + public static Fn2 set(Lens lens) { + return Set.set().apply(lens); + } + + public static Fn1 set(Lens lens, B b) { + return set(lens).apply(b); + } + + public static T set(Lens lens, B b, S s) { + return set(lens, b).apply(s); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/lens/functions/View.java b/src/main/java/com/jnape/palatable/lambda/lens/functions/View.java new file mode 100644 index 000000000..1d12df267 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/lens/functions/View.java @@ -0,0 +1,29 @@ +package com.jnape.palatable.lambda.lens.functions; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functor.builtin.Const; +import com.jnape.palatable.lambda.lens.Lens; + +public final class View implements Fn2, S, A> { + + private View() { + } + + @Override + public A apply(Lens lens, S s) { + return lens., Const>fix().apply(Const::new, s).runConst(); + } + + public static View view() { + return new View<>(); + } + + public static Fn1 view(Lens lens) { + return View.view().apply(lens); + } + + public static A view(Lens lens, S s) { + return view(lens).apply(s); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/functor/builtin/ConstTest.java b/src/test/java/com/jnape/palatable/lambda/functor/builtin/ConstTest.java new file mode 100644 index 000000000..5d8a06abc --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functor/builtin/ConstTest.java @@ -0,0 +1,18 @@ +package com.jnape.palatable.lambda.functor.builtin; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class ConstTest { + + @Test + public void functorProperties() { + assertEquals("foo", new Const("foo").fmap(x -> x + 1).runConst()); + } + + @Test + public void bifunctorProperties() { + assertEquals("FOO", new Const("foo").biMap(String::toUpperCase, x -> x + 1).runConst()); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functor/builtin/IdentityTest.java b/src/test/java/com/jnape/palatable/lambda/functor/builtin/IdentityTest.java new file mode 100644 index 000000000..792980e99 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functor/builtin/IdentityTest.java @@ -0,0 +1,13 @@ +package com.jnape.palatable.lambda.functor.builtin; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class IdentityTest { + + @Test + public void functorProperties() { + assertEquals("FOO", new Identity<>("foo").fmap(String::toUpperCase).runIdentity()); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java new file mode 100644 index 000000000..f1605e7ad --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java @@ -0,0 +1,50 @@ +package com.jnape.palatable.lambda.lens; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.builtin.Const; +import com.jnape.palatable.lambda.functor.builtin.Identity; +import org.junit.Test; + +import java.util.List; +import java.util.Set; + +import static java.util.Arrays.asList; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertEquals; + +public class LensTest { + + private static final Lens, Set, String, Integer> LENS = Lens.lens(xs -> xs.get(0), (xs, i) -> singleton(i)); + + @Test + public void setsUnderIdentity() { + Set ints = LENS.>, Identity>apply( + s -> new Identity<>(s.length()), + asList("foo", "bar", "baz") + ).runIdentity(); + + assertEquals(singleton(3), ints); + } + + @Test + public void viewsUnderConst() { + Integer i = LENS.>, Const>apply( + s -> new Const<>(s.length()), + asList("foo", "bar", "baz") + ).runConst(); + + assertEquals((Integer) 3, i); + } + + @Test + public void fix() { + Fn1> fn = s -> new Const<>(s.length()); + List s = singletonList("foo"); + + Integer fixedLensResult = LENS.>, Const>fix().apply(fn, s).runConst(); + Integer unfixedLensResult = LENS.>, Const>apply(fn, s).runConst(); + + assertEquals(unfixedLensResult, fixedLensResult); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/functions/OverTest.java b/src/test/java/com/jnape/palatable/lambda/lens/functions/OverTest.java new file mode 100644 index 000000000..e5a95cf4a --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/lens/functions/OverTest.java @@ -0,0 +1,22 @@ +package com.jnape.palatable.lambda.lens.functions; + +import com.jnape.palatable.lambda.lens.Lens; +import org.junit.Test; + +import java.util.List; +import java.util.Set; + +import static com.jnape.palatable.lambda.lens.Lens.lens; +import static com.jnape.palatable.lambda.lens.functions.Over.over; +import static java.util.Arrays.asList; +import static java.util.Collections.singleton; +import static org.junit.Assert.assertEquals; + +public class OverTest { + + @Test + public void mapsDataWithLensAndFunction() { + Lens, Set, String, Integer> lens = lens(xs -> xs.get(0), (xs, i) -> singleton(i)); + assertEquals(singleton(1), over(lens, String::length, asList("a", "aa", "aaa"))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/functions/SetTest.java b/src/test/java/com/jnape/palatable/lambda/lens/functions/SetTest.java new file mode 100644 index 000000000..47fa8a959 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/lens/functions/SetTest.java @@ -0,0 +1,21 @@ +package com.jnape.palatable.lambda.lens.functions; + +import com.jnape.palatable.lambda.lens.Lens; +import org.junit.Test; + +import java.util.List; + +import static com.jnape.palatable.lambda.lens.Lens.lens; +import static com.jnape.palatable.lambda.lens.functions.Set.set; +import static java.util.Arrays.asList; +import static java.util.Collections.singleton; +import static org.junit.Assert.assertEquals; + +public class SetTest { + + @Test + public void updatesWithLensAndNewValue() { + Lens, java.util.Set, String, Integer> lens = lens(xs -> xs.get(0), (xs, i) -> singleton(i)); + assertEquals(singleton(5), set(lens, 5, asList("a", "aa", "aaa"))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/functions/ViewTest.java b/src/test/java/com/jnape/palatable/lambda/lens/functions/ViewTest.java new file mode 100644 index 000000000..201ec990a --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/lens/functions/ViewTest.java @@ -0,0 +1,22 @@ +package com.jnape.palatable.lambda.lens.functions; + +import com.jnape.palatable.lambda.lens.Lens; +import org.junit.Test; + +import java.util.List; +import java.util.Set; + +import static com.jnape.palatable.lambda.lens.Lens.lens; +import static com.jnape.palatable.lambda.lens.functions.View.view; +import static java.util.Arrays.asList; +import static java.util.Collections.singleton; +import static org.junit.Assert.assertEquals; + +public class ViewTest { + + @Test + public void viewsSubPartWithLens() { + Lens, Set, String, Integer> lens = lens(xs -> xs.get(0), (xs, i) -> singleton(i)); + assertEquals("foo", view(lens, asList("foo", "bar", "baz"))); + } +} \ No newline at end of file From 36f30df0ed15a6b62158fa4b8c5b9cee43efa04f Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 16 Jul 2016 16:32:25 -0500 Subject: [PATCH 02/29] Adding lens composition and initial unsafe map lenses --- pom.xml | 10 ++- .../com/jnape/palatable/lambda/lens/Lens.java | 20 ++++- .../lambda/lens/lenses/UnsafeMapLens.java | 55 ++++++++++++ .../jnape/palatable/lambda/lens/LensTest.java | 27 +++++- .../lambda/lens/lenses/UnsafeMapLensTest.java | 86 +++++++++++++++++++ 5 files changed, 195 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/lens/lenses/UnsafeMapLens.java create mode 100644 src/test/java/com/jnape/palatable/lambda/lens/lenses/UnsafeMapLensTest.java diff --git a/pom.xml b/pom.xml index d3b197e90..4acf5e12c 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,6 @@ - + 4.0.0 @@ -56,6 +57,7 @@ 3.1 1.0 3.3 + 1.3 @@ -63,6 +65,12 @@ junit junit + + org.hamcrest + hamcrest-all + ${hamcrest-all.version} + test + org.mockito mockito-all diff --git a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java index c015b5cce..830c00057 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java @@ -4,8 +4,13 @@ import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.functor.Functor; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.lens.functions.Over.over; +import static com.jnape.palatable.lambda.lens.functions.Set.set; +import static com.jnape.palatable.lambda.lens.functions.View.view; + @FunctionalInterface -public interface Lens { +public interface Lens extends Functor { , FB extends Functor> FT apply(Fn1 fn, S s); @@ -13,6 +18,19 @@ default , FB extends Functor> Fixed return this::apply; } + @Override + default Lens fmap(Fn1 fn) { + return this.compose(Lens.lens(id(), (s, t) -> fn.apply(t))); + } + + default Lens andThen(Lens f) { + return f.compose(this); + } + + default Lens compose(Lens g) { + return lens(view(g).fmap(view(this)), (q, b) -> over(g, set(this, b), q)); + } + static Lens lens(Fn1 getter, Fn2 setter) { return new Lens() { diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/UnsafeMapLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/UnsafeMapLens.java new file mode 100644 index 000000000..8d57de8af --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/UnsafeMapLens.java @@ -0,0 +1,55 @@ +package com.jnape.palatable.lambda.lens.lenses; + +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.lens.Lens; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import static com.jnape.palatable.lambda.lens.Lens.lens; +import static com.jnape.palatable.lambda.lens.Lens.simpleLens; +import static com.jnape.palatable.lambda.lens.functions.View.view; + +public final class UnsafeMapLens { + + private UnsafeMapLens() { + } + + public static Lens.Simple, V> atKey(K k) { + return simpleLens(m -> m.get(k), (m, v) -> { + m.put(k, v); + return m; + }); + } + + public static Lens.Simple, Set> keys() { + return simpleLens(Map::keySet, (m, ks) -> { + Set keys = m.keySet(); + keys.retainAll(ks); + ks.removeAll(keys); + ks.forEach(k -> m.put(k, null)); + return m; + }); + } + + public static Lens, Map, Collection, Fn2> values() { + return lens(Map::values, (m, kvFn) -> { + m.entrySet().forEach(entry -> entry.setValue(kvFn.apply(entry.getKey(), entry.getValue()))); + return m; + }); + } + + public static Lens.Simple, Map> invert() { + return simpleLens(m -> { + Map inverted = new HashMap<>(); + m.entrySet().forEach(entry -> inverted.put(entry.getValue(), entry.getKey())); + return inverted; + }, (m, im) -> { + m.clear(); + m.putAll(view(invert(), im)); + return m; + }); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java index f1605e7ad..84609ba26 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java @@ -6,16 +6,22 @@ import org.junit.Test; import java.util.List; +import java.util.Map; import java.util.Set; +import static com.jnape.palatable.lambda.lens.Lens.lens; +import static com.jnape.palatable.lambda.lens.functions.Set.set; +import static com.jnape.palatable.lambda.lens.functions.View.view; import static java.util.Arrays.asList; import static java.util.Collections.singleton; import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; import static org.junit.Assert.assertEquals; public class LensTest { - private static final Lens, Set, String, Integer> LENS = Lens.lens(xs -> xs.get(0), (xs, i) -> singleton(i)); + private static final Lens>, Map>, List, Set> EARLIER_LENS = lens(m -> m.get("foo"), (m, s) -> singletonMap("foo", s)); + private static final Lens, Set, String, Integer> LENS = lens(xs -> xs.get(0), (xs, i) -> singleton(i)); @Test public void setsUnderIdentity() { @@ -47,4 +53,23 @@ public void fix() { assertEquals(unfixedLensResult, fixedLensResult); } + + @Test + public void functorProperties() { + assertEquals(false, set(LENS.fmap(Set::isEmpty), 1, singletonList("foo"))); + } + + @Test + public void composition() { + Map> map = singletonMap("foo", asList("one", "two", "three")); + assertEquals("one", view(LENS.compose(EARLIER_LENS), map)); + assertEquals(singletonMap("foo", singleton(1)), set(LENS.compose(EARLIER_LENS), 1, map)); + } + + @Test + public void andThenComposesInReverse() { + Map> map = singletonMap("foo", asList("one", "two", "three")); + assertEquals("one", view(EARLIER_LENS.andThen(LENS), map)); + assertEquals(singletonMap("foo", singleton(1)), set(EARLIER_LENS.andThen(LENS), 1, map)); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/UnsafeMapLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/UnsafeMapLensTest.java new file mode 100644 index 000000000..d1dceac4b --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/UnsafeMapLensTest.java @@ -0,0 +1,86 @@ +package com.jnape.palatable.lambda.lens.lenses; + +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.lens.Lens; +import org.junit.Before; +import org.junit.Test; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static com.jnape.palatable.lambda.lens.functions.Over.over; +import static com.jnape.palatable.lambda.lens.functions.Set.set; +import static com.jnape.palatable.lambda.lens.functions.View.view; +import static com.jnape.palatable.lambda.lens.lenses.UnsafeMapLens.keys; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; + +public class UnsafeMapLensTest { + + private Map m; + + @Before + public void setUp() { + m = new HashMap() {{ + put("foo", 1); + put("bar", 2); + put("baz", 3); + }}; + } + + @Test + public void atKeyFocusesOnValueAtKey() { + Lens, Map, Integer, Integer> atFoo = UnsafeMapLens.atKey("foo"); + + assertEquals((Integer) 1, view(atFoo, m)); + assertEquals((Integer) (-1), view(atFoo, set(atFoo, -1, m))); + assertEquals((Integer) (-2), view(atFoo, over(atFoo, x -> x * 2, m))); + } + + @Test + public void keysFocusOnKeys() { + Lens, Map, Set, Set> keys = keys(); + + assertEquals(m.keySet(), view(keys, m)); + assertEquals(new HashMap() {{ + put("bar", 2); + put("baz", 3); + put("quux", null); + }}, set(keys, new HashSet<>(asList("bar", "baz", "quux")), m)); + } + + @Test + public void valuesFocusOnValues() { + Lens, Map, Collection, Fn2> values = UnsafeMapLens.values(); + + assertEquals(m.values(), view(values, m)); + assertEquals(new HashMap() {{ + put("foo", 4); + put("bar", 5); + put("baz", 6); + }}, + set(values, (k, v) -> k.length() + v, m)); + } + + @Test + public void invertFlipsKeysAndValues() { + Lens.Simple, Map> invert = UnsafeMapLens.invert(); + + assertEquals(new HashMap() {{ + put(1, "foo"); + put(2, "bar"); + put(3, "baz"); + }}, view(invert, m)); + + assertEquals(new HashMap() {{ + put("bar", 2); + put("baz", 3); + }}, set(invert, new HashMap() {{ + put(2, "bar"); + put(3, "baz"); + }}, m)); + } +} \ No newline at end of file From d0486653e2499dbf7ac60b7c982b26c3d21268a7 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 16 Jul 2016 17:01:43 -0500 Subject: [PATCH 03/29] Adding Either#toOptional --- .../com/jnape/palatable/lambda/adt/Either.java | 4 ++++ .../jnape/palatable/lambda/adt/EitherTest.java | 15 +++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/adt/Either.java b/src/main/java/com/jnape/palatable/lambda/adt/Either.java index e6481194b..13d04db8f 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/Either.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/Either.java @@ -108,6 +108,10 @@ public final Either biMap(Fn1 leftFn, return match(l -> left(leftFn.apply(l)), r -> right(rightFn.apply(r))); } + public Optional toOptional() { + return match(__ -> Optional.empty(), Optional::ofNullable); + } + public static Either fromOptional(Optional optional, Supplier leftFn) { return optional.>map(Either::right) .orElse(left(leftFn.get())); diff --git a/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java b/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java index 21cd82655..53e4644cf 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java @@ -116,13 +116,20 @@ public void matchDuallyLiftsAndFlattens() { assertThat(right.match(l -> l + "bar", r -> r + 1), is(2)); } + @Test + public void toOptionalMapsEitherToOptional() { + assertEquals(Optional.of(1), Either.right(1).toOptional()); + assertEquals(Optional.empty(), Either.right(null).toOptional()); + assertEquals(Optional.empty(), Either.left("fail").toOptional()); + } + @Test public void fromOptionalMapsOptionalToEither() { - Optional present = Optional.of("foo"); - Optional absent = Optional.empty(); + Optional present = Optional.of(1); + Optional absent = Optional.empty(); - assertThat(fromOptional(present, () -> -1), is(right("foo"))); - assertThat(fromOptional(absent, () -> -1), is(left(-1))); + assertThat(fromOptional(present, () -> "fail"), is(right(1))); + assertThat(fromOptional(absent, () -> "fail"), is(left("fail"))); } @Test From 7abd5ace1cd524c65e2f27a08061d2a3098f3216 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 16 Jul 2016 18:16:47 -0500 Subject: [PATCH 04/29] Adding impure list and collection lenses; renaming UnsafeMapLens to ImpureMapLens --- .../lens/lenses/ImpureCollectionLens.java | 28 +++++++++ .../lambda/lens/lenses/ImpureListLens.java | 22 +++++++ ...{UnsafeMapLens.java => ImpureMapLens.java} | 4 +- .../lens/lenses/ImpureCollectionLensTest.java | 59 +++++++++++++++++++ .../lens/lenses/ImpureListLensTest.java | 38 ++++++++++++ ...apLensTest.java => ImpureMapLensTest.java} | 10 ++-- 6 files changed, 154 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureCollectionLens.java create mode 100644 src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureListLens.java rename src/main/java/com/jnape/palatable/lambda/lens/lenses/{UnsafeMapLens.java => ImpureMapLens.java} (96%) create mode 100644 src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureCollectionLensTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureListLensTest.java rename src/test/java/com/jnape/palatable/lambda/lens/lenses/{UnsafeMapLensTest.java => ImpureMapLensTest.java} (91%) diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureCollectionLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureCollectionLens.java new file mode 100644 index 000000000..694df1c01 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureCollectionLens.java @@ -0,0 +1,28 @@ +package com.jnape.palatable.lambda.lens.lenses; + +import com.jnape.palatable.lambda.lens.Lens; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Stream; + +import static com.jnape.palatable.lambda.lens.Lens.simpleLens; + +public class ImpureCollectionLens { + + public static > Lens.Simple> asSet() { + return simpleLens(HashSet::new, (xsL, xsS) -> { + xsL.retainAll(xsS); + return xsL; + }); + } + + public static > Lens.Simple> asStream() { + return simpleLens(Collection::stream, (xsL, xsS) -> { + xsL.clear(); + xsS.forEach(xsL::add); + return xsL; + }); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureListLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureListLens.java new file mode 100644 index 000000000..8b34380d6 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureListLens.java @@ -0,0 +1,22 @@ +package com.jnape.palatable.lambda.lens.lenses; + +import com.jnape.palatable.lambda.lens.Lens; + +import java.util.List; + +import static com.jnape.palatable.lambda.lens.Lens.simpleLens; + +public final class ImpureListLens { + + private ImpureListLens() { + } + + public static Lens.Simple, X> at(int index) { + return simpleLens(xs -> xs.size() > index ? xs.get(index) : null, + (xs, x) -> { + if (xs.size() > index) + xs.set(index, x); + return xs; + }); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/UnsafeMapLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureMapLens.java similarity index 96% rename from src/main/java/com/jnape/palatable/lambda/lens/lenses/UnsafeMapLens.java rename to src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureMapLens.java index 8d57de8af..75db1c09c 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/UnsafeMapLens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureMapLens.java @@ -12,9 +12,9 @@ import static com.jnape.palatable.lambda.lens.Lens.simpleLens; import static com.jnape.palatable.lambda.lens.functions.View.view; -public final class UnsafeMapLens { +public final class ImpureMapLens { - private UnsafeMapLens() { + private ImpureMapLens() { } public static Lens.Simple, V> atKey(K k) { diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureCollectionLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureCollectionLensTest.java new file mode 100644 index 000000000..2bd72995d --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureCollectionLensTest.java @@ -0,0 +1,59 @@ +package com.jnape.palatable.lambda.lens.lenses; + +import com.jnape.palatable.lambda.lens.Lens; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + +import static com.jnape.palatable.lambda.lens.functions.Set.set; +import static com.jnape.palatable.lambda.lens.functions.View.view; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; +import static java.util.stream.Collectors.toList; +import static org.junit.Assert.assertEquals; + +public class ImpureCollectionLensTest { + + private List xs; + + @Before + public void setUp() { + xs = new ArrayList() {{ + add("foo"); + add("bar"); + add("baz"); + }}; + } + + @Test + public void asSetFocusesOnCollectionAsSet() { + Lens.Simple, Set> asSet = ImpureCollectionLens.asSet(); + + assertEquals(new HashSet<>(xs), view(asSet, xs)); + assertEquals(singleton("foo"), view(asSet, asList("foo", "foo"))); + assertEquals(emptySet(), view(asSet, emptyList())); + + assertEquals(asList("foo", "bar"), set(asSet, new HashSet<>(asList("foo", "bar")), xs)); + assertEquals(asList("foo", "foo", "bar"), + set(asSet, + new HashSet<>(asList("foo", "bar")), + new ArrayList<>(asList("foo", "foo", "bar", "baz")))); + assertEquals(emptyList(), set(asSet, emptySet(), xs)); + assertEquals(emptyList(), set(asSet, singleton("foo"), emptyList())); + } + + @Test + public void asStreamFocusesOnCollectionAsStream() { + Lens.Simple, Stream> asStream = ImpureCollectionLens.asStream(); + + assertEquals(xs, view(asStream, xs).collect(toList())); + assertEquals(asList("foo", "bar"), set(asStream, Stream.of("foo", "bar"), xs)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureListLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureListLensTest.java new file mode 100644 index 000000000..f908dfe3c --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureListLensTest.java @@ -0,0 +1,38 @@ +package com.jnape.palatable.lambda.lens.lenses; + +import com.jnape.palatable.lambda.lens.Lens; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static com.jnape.palatable.lambda.lens.functions.Set.set; +import static com.jnape.palatable.lambda.lens.functions.View.view; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static org.junit.Assert.assertEquals; + +public class ImpureListLensTest { + + private List xs; + + @Before + public void setUp() throws Exception { + xs = new ArrayList() {{ + add("foo"); + add("bar"); + add("baz"); + }}; + } + + @Test + public void atFocusesOnElementAtIndex() { + Lens.Simple, String> at0 = ImpureListLens.at(0); + + assertEquals("foo", view(at0, xs)); + assertEquals(null, view(at0, emptyList())); + assertEquals(asList("quux", "bar", "baz"), set(at0, "quux", xs)); + assertEquals(emptyList(), set(at0, "quux", emptyList())); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/UnsafeMapLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureMapLensTest.java similarity index 91% rename from src/test/java/com/jnape/palatable/lambda/lens/lenses/UnsafeMapLensTest.java rename to src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureMapLensTest.java index d1dceac4b..247c1df79 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/UnsafeMapLensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureMapLensTest.java @@ -14,11 +14,11 @@ import static com.jnape.palatable.lambda.lens.functions.Over.over; import static com.jnape.palatable.lambda.lens.functions.Set.set; import static com.jnape.palatable.lambda.lens.functions.View.view; -import static com.jnape.palatable.lambda.lens.lenses.UnsafeMapLens.keys; +import static com.jnape.palatable.lambda.lens.lenses.ImpureMapLens.keys; import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; -public class UnsafeMapLensTest { +public class ImpureMapLensTest { private Map m; @@ -33,7 +33,7 @@ public void setUp() { @Test public void atKeyFocusesOnValueAtKey() { - Lens, Map, Integer, Integer> atFoo = UnsafeMapLens.atKey("foo"); + Lens, Map, Integer, Integer> atFoo = ImpureMapLens.atKey("foo"); assertEquals((Integer) 1, view(atFoo, m)); assertEquals((Integer) (-1), view(atFoo, set(atFoo, -1, m))); @@ -54,7 +54,7 @@ public void keysFocusOnKeys() { @Test public void valuesFocusOnValues() { - Lens, Map, Collection, Fn2> values = UnsafeMapLens.values(); + Lens, Map, Collection, Fn2> values = ImpureMapLens.values(); assertEquals(m.values(), view(values, m)); assertEquals(new HashMap() {{ @@ -67,7 +67,7 @@ public void valuesFocusOnValues() { @Test public void invertFlipsKeysAndValues() { - Lens.Simple, Map> invert = UnsafeMapLens.invert(); + Lens.Simple, Map> invert = ImpureMapLens.invert(); assertEquals(new HashMap() {{ put(1, "foo"); From 2f6790c88107ca5c4ea8b61d90b1d92e385912f6 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 16 Jul 2016 18:18:20 -0500 Subject: [PATCH 05/29] moving impure lenses to impure package of shame --- .../lambda/lens/lenses/{ => impure}/ImpureCollectionLens.java | 2 +- .../lambda/lens/lenses/{ => impure}/ImpureListLens.java | 2 +- .../lambda/lens/lenses/{ => impure}/ImpureMapLens.java | 2 +- .../lens/lenses/{ => impure}/ImpureCollectionLensTest.java | 2 +- .../lambda/lens/lenses/{ => impure}/ImpureListLensTest.java | 2 +- .../lambda/lens/lenses/{ => impure}/ImpureMapLensTest.java | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) rename src/main/java/com/jnape/palatable/lambda/lens/lenses/{ => impure}/ImpureCollectionLens.java (92%) rename src/main/java/com/jnape/palatable/lambda/lens/lenses/{ => impure}/ImpureListLens.java (91%) rename src/main/java/com/jnape/palatable/lambda/lens/lenses/{ => impure}/ImpureMapLens.java (96%) rename src/test/java/com/jnape/palatable/lambda/lens/lenses/{ => impure}/ImpureCollectionLensTest.java (97%) rename src/test/java/com/jnape/palatable/lambda/lens/lenses/{ => impure}/ImpureListLensTest.java (94%) rename src/test/java/com/jnape/palatable/lambda/lens/lenses/{ => impure}/ImpureMapLensTest.java (95%) diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureCollectionLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureCollectionLens.java similarity index 92% rename from src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureCollectionLens.java rename to src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureCollectionLens.java index 694df1c01..dc5b838af 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureCollectionLens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureCollectionLens.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.lens.lenses; +package com.jnape.palatable.lambda.lens.lenses.impure; import com.jnape.palatable.lambda.lens.Lens; diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureListLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureListLens.java similarity index 91% rename from src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureListLens.java rename to src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureListLens.java index 8b34380d6..3a3b92793 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureListLens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureListLens.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.lens.lenses; +package com.jnape.palatable.lambda.lens.lenses.impure; import com.jnape.palatable.lambda.lens.Lens; diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureMapLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLens.java similarity index 96% rename from src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureMapLens.java rename to src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLens.java index 75db1c09c..298b3330f 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureMapLens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLens.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.lens.lenses; +package com.jnape.palatable.lambda.lens.lenses.impure; import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.lens.Lens; diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureCollectionLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureCollectionLensTest.java similarity index 97% rename from src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureCollectionLensTest.java rename to src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureCollectionLensTest.java index 2bd72995d..9d8e20097 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureCollectionLensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureCollectionLensTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.lens.lenses; +package com.jnape.palatable.lambda.lens.lenses.impure; import com.jnape.palatable.lambda.lens.Lens; import org.junit.Before; diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureListLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureListLensTest.java similarity index 94% rename from src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureListLensTest.java rename to src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureListLensTest.java index f908dfe3c..03e7eda39 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureListLensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureListLensTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.lens.lenses; +package com.jnape.palatable.lambda.lens.lenses.impure; import com.jnape.palatable.lambda.lens.Lens; import org.junit.Before; diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureMapLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLensTest.java similarity index 95% rename from src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureMapLensTest.java rename to src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLensTest.java index 247c1df79..de0ee33a3 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureMapLensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLensTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.lens.lenses; +package com.jnape.palatable.lambda.lens.lenses.impure; import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.lens.Lens; @@ -14,7 +14,7 @@ import static com.jnape.palatable.lambda.lens.functions.Over.over; import static com.jnape.palatable.lambda.lens.functions.Set.set; import static com.jnape.palatable.lambda.lens.functions.View.view; -import static com.jnape.palatable.lambda.lens.lenses.ImpureMapLens.keys; +import static com.jnape.palatable.lambda.lens.lenses.impure.ImpureMapLens.keys; import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; From 1afa2498fdf1f5d3398052fba1e82cc9ff08d3a5 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 16 Jul 2016 18:48:44 -0500 Subject: [PATCH 06/29] adding Optional and Either lenses --- .../lambda/lens/lenses/EitherLens.java | 23 +++++++++++ .../lambda/lens/lenses/OptionalLens.java | 17 ++++++++ .../lambda/lens/lenses/EitherLensTest.java | 40 +++++++++++++++++++ .../lambda/lens/lenses/OptionalLensTest.java | 23 +++++++++++ 4 files changed, 103 insertions(+) create mode 100644 src/main/java/com/jnape/palatable/lambda/lens/lenses/EitherLens.java create mode 100644 src/main/java/com/jnape/palatable/lambda/lens/lenses/OptionalLens.java create mode 100644 src/test/java/com/jnape/palatable/lambda/lens/lenses/EitherLensTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/lens/lenses/OptionalLensTest.java diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/EitherLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/EitherLens.java new file mode 100644 index 000000000..cb92c6dda --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/EitherLens.java @@ -0,0 +1,23 @@ +package com.jnape.palatable.lambda.lens.lenses; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.lens.Lens; + +import java.util.Optional; + +import static com.jnape.palatable.lambda.lens.Lens.simpleLens; + +public final class EitherLens { + + private EitherLens() { + } + + public static Lens.Simple, Optional> right() { + return simpleLens(Either::toOptional, (lOrR, optR) -> optR.>map(Either::right).orElse(lOrR)); + } + + public static Lens.Simple, Optional> left() { + return simpleLens(e -> e.match(Optional::ofNullable, __ -> Optional.empty()), + (lOrR, optL) -> optL.>map(Either::left).orElse(lOrR)); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/OptionalLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/OptionalLens.java new file mode 100644 index 000000000..88b562574 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/OptionalLens.java @@ -0,0 +1,17 @@ +package com.jnape.palatable.lambda.lens.lenses; + +import com.jnape.palatable.lambda.lens.Lens; + +import java.util.Optional; + +import static com.jnape.palatable.lambda.lens.Lens.simpleLens; + +public final class OptionalLens { + + private OptionalLens() { + } + + public static Lens.Simple> asOptional() { + return simpleLens(Optional::ofNullable, (v, optV) -> optV.orElse(v)); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/EitherLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/EitherLensTest.java new file mode 100644 index 000000000..fb518dd9b --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/EitherLensTest.java @@ -0,0 +1,40 @@ +package com.jnape.palatable.lambda.lens.lenses; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.lens.Lens; +import org.junit.Test; + +import java.util.Optional; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.lens.functions.Set.set; +import static com.jnape.palatable.lambda.lens.functions.View.view; +import static org.junit.Assert.assertEquals; + +public class EitherLensTest { + + @Test + public void rightFocusesOnRightValues() { + Lens.Simple, Optional> right = EitherLens.right(); + + assertEquals(Optional.of(1), view(right, right(1))); + assertEquals(Optional.empty(), view(right, left("fail"))); + assertEquals(right(2), set(right, Optional.of(2), right(1))); + assertEquals(right(1), set(right, Optional.empty(), right(1))); + assertEquals(right(2), set(right, Optional.of(2), left("fail"))); + assertEquals(left("fail"), set(right, Optional.empty(), left("fail"))); + } + + @Test + public void leftFocusesOnLeftValues() { + Lens.Simple, Optional> left = EitherLens.left(); + + assertEquals(Optional.of("fail"), view(left, left("fail"))); + assertEquals(Optional.empty(), view(left, right(1))); + assertEquals(left("foo"), set(left, Optional.of("foo"), left("fail"))); + assertEquals(left("fail"), set(left, Optional.empty(), left("fail"))); + assertEquals(left("foo"), set(left, Optional.of("foo"), right(1))); + assertEquals(right(1), set(left, Optional.empty(), right(1))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/OptionalLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/OptionalLensTest.java new file mode 100644 index 000000000..708a57704 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/OptionalLensTest.java @@ -0,0 +1,23 @@ +package com.jnape.palatable.lambda.lens.lenses; + +import com.jnape.palatable.lambda.lens.Lens; +import org.junit.Test; + +import java.util.Optional; + +import static com.jnape.palatable.lambda.lens.functions.Set.set; +import static com.jnape.palatable.lambda.lens.functions.View.view; +import static org.junit.Assert.assertEquals; + +public class OptionalLensTest { + + @Test + public void asOptionalWrapsValuesInOptional() { + Lens.Simple> asOptional = OptionalLens.asOptional(); + + assertEquals(Optional.of("foo"), view(asOptional, "foo")); + assertEquals(Optional.empty(), view(asOptional, null)); + assertEquals("bar", set(asOptional, Optional.of("bar"), "foo")); + assertEquals("foo", set(asOptional, Optional.empty(), "foo")); + } +} \ No newline at end of file From 35a28f7f0015555c080504972e918b3cbbd27829 Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 17 Jul 2016 15:32:06 -0500 Subject: [PATCH 07/29] adding referentially transparent map lenses; Lens.Simple now has its own Simple overloads --- .../com/jnape/palatable/lambda/lens/Lens.java | 21 ++++ .../palatable/lambda/lens/lenses/MapLens.java | 38 +++++++ .../lens/lenses/impure/ImpureMapLens.java | 4 +- .../lambda/lens/lenses/MapLensTest.java | 107 ++++++++++++++++++ .../lens/lenses/impure/ImpureMapLensTest.java | 49 +++++--- 5 files changed, 199 insertions(+), 20 deletions(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java create mode 100644 src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java diff --git a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java index 830c00057..4dd5c96e8 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java @@ -49,9 +49,30 @@ static Lens.Simple simpleLens(Fn1 getter, return lens(getter, setter)::apply; } + @FunctionalInterface interface Simple extends Lens { + + @Override + default , FA extends Functor> Fixed fix() { + return Lens.super.fix()::apply; + } + + @SuppressWarnings("unchecked") + default Lens.Simple compose(Lens.Simple g) { + return Lens.super.compose(g)::apply; + } + + default Lens.Simple andThen(Lens.Simple f) { + return f.compose(this); + } + + @FunctionalInterface + interface Fixed, FA extends Functor> + extends Lens.Fixed { + } } + @FunctionalInterface interface Fixed, FB extends Functor> extends Fn2, S, FT> { } diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java new file mode 100644 index 000000000..ae3442830 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java @@ -0,0 +1,38 @@ +package com.jnape.palatable.lambda.lens.lenses; + +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.lens.Lens; +import com.jnape.palatable.lambda.lens.lenses.impure.ImpureMapLens; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import static com.jnape.palatable.lambda.lens.Lens.simpleLens; + +public class MapLens { + + private MapLens() { + } + + public static Lens.Simple, Map> asCopy() { + return simpleLens(HashMap::new, (__, copy) -> copy); + } + + public static Lens.Simple, V> atKey(K k) { + return ImpureMapLens.atKey(k).compose(asCopy()); + } + + public static Lens.Simple, Set> keys() { + return ImpureMapLens.keys().compose(asCopy()); + } + + public static Lens, Map, Collection, Fn2> values() { + return ImpureMapLens.values().compose(asCopy()); + } + + public static Lens.Simple, Map> inverted() { + return ImpureMapLens.inverted().compose(asCopy()); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLens.java index 298b3330f..065e6c334 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLens.java @@ -41,14 +41,14 @@ public static Lens, Map, Collection, Fn2> val }); } - public static Lens.Simple, Map> invert() { + public static Lens.Simple, Map> inverted() { return simpleLens(m -> { Map inverted = new HashMap<>(); m.entrySet().forEach(entry -> inverted.put(entry.getValue(), entry.getKey())); return inverted; }, (m, im) -> { m.clear(); - m.putAll(view(invert(), im)); + m.putAll(view(inverted(), im)); return m; }); } diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java new file mode 100644 index 000000000..69d7e3f5e --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java @@ -0,0 +1,107 @@ +package com.jnape.palatable.lambda.lens.lenses; + +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.lens.Lens; +import org.junit.Before; +import org.junit.Test; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static com.jnape.palatable.lambda.lens.functions.Set.set; +import static com.jnape.palatable.lambda.lens.functions.View.view; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonMap; +import static java.util.Collections.unmodifiableMap; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThat; + +public class MapLensTest { + + private Map m; + + @Before + public void setUp() throws Exception { + m = unmodifiableMap(new HashMap() {{ + put("foo", 1); + put("bar", 2); + put("baz", 3); + }}); + } + + @Test + public void asCopyFocusesOnMapThroughCopy() { + Lens.Simple, Map> asCopy = MapLens.asCopy(); + + assertEquals(m, view(asCopy, m)); + assertNotSame(m, view(asCopy, m)); + + Map update = new HashMap() {{ + put("quux", 0); + }}; + assertEquals(update, set(asCopy, update, m)); + assertSame(update, set(asCopy, update, m)); + } + + @Test + public void atKeyFocusesOnValueAtKey() { + Lens.Simple, Integer> atFoo = MapLens.atKey("foo"); + + assertEquals((Integer) 1, view(atFoo, m)); + assertEquals(new HashMap() {{ + put("foo", -1); + put("bar", 2); + put("baz", 3); + }}, set(atFoo, -1, m)); + } + + @Test + public void keysFocusesOnKeysWithImmutableSet() { + Lens.Simple, Set> keys = MapLens.keys(); + + assertEquals(m.keySet(), view(keys, m)); + view(keys, m).clear(); + assertEquals(new HashSet<>(asList("foo", "bar", "baz")), m.keySet()); + + assertEquals(new HashMap() {{ + put("bar", 2); + put("baz", 3); + put("quux", null); + }}, set(keys, new HashSet<>(asList("bar", "baz", "quux")), m)); + } + + @Test + public void valuesFocusesOnValues() { + Lens, Map, Collection, Fn2> values = MapLens.values(); + + assertThat(view(values, m), + containsInAnyOrder(m.values().toArray())); + + assertEquals(new HashMap() {{ + put("foo", 4); + put("bar", 5); + put("baz", 6); + }}, set(values, (k, v) -> k.length() + v, m)); + } + + @Test + public void invertedFocusesOnMapWithKeysAndValuesSwitched() { + Lens.Simple, Map> inverted = MapLens.inverted(); + + assertEquals(new HashMap() {{ + put(1, "foo"); + put(2, "bar"); + put(3, "baz"); + }}, view(inverted, m)); + + assertEquals(new HashMap() {{ + put("quux", -1); + }}, set(inverted, singletonMap(-1, "quux"), m)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLensTest.java index de0ee33a3..a90c89abc 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLensTest.java @@ -11,12 +11,12 @@ import java.util.Map; import java.util.Set; -import static com.jnape.palatable.lambda.lens.functions.Over.over; import static com.jnape.palatable.lambda.lens.functions.Set.set; import static com.jnape.palatable.lambda.lens.functions.View.view; import static com.jnape.palatable.lambda.lens.lenses.impure.ImpureMapLens.keys; import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; public class ImpureMapLensTest { @@ -36,51 +36,64 @@ public void atKeyFocusesOnValueAtKey() { Lens, Map, Integer, Integer> atFoo = ImpureMapLens.atKey("foo"); assertEquals((Integer) 1, view(atFoo, m)); - assertEquals((Integer) (-1), view(atFoo, set(atFoo, -1, m))); - assertEquals((Integer) (-2), view(atFoo, over(atFoo, x -> x * 2, m))); + + Map updated = set(atFoo, -1, m); + assertEquals(new HashMap() {{ + put("foo", -1); + put("bar", 2); + put("baz", 3); + }}, updated); + assertSame(m, updated); } @Test - public void keysFocusOnKeys() { + public void keysFocusesOnKeys() { Lens, Map, Set, Set> keys = keys(); assertEquals(m.keySet(), view(keys, m)); + + Map updated = set(keys, new HashSet<>(asList("bar", "baz", "quux")), m); assertEquals(new HashMap() {{ put("bar", 2); put("baz", 3); put("quux", null); - }}, set(keys, new HashSet<>(asList("bar", "baz", "quux")), m)); + }}, updated); + assertSame(m, updated); } @Test - public void valuesFocusOnValues() { + public void valuesFocusesOnValues() { Lens, Map, Collection, Fn2> values = ImpureMapLens.values(); assertEquals(m.values(), view(values, m)); + + Map updated = set(values, (k, v) -> k.length() + v, m); assertEquals(new HashMap() {{ - put("foo", 4); - put("bar", 5); - put("baz", 6); - }}, - set(values, (k, v) -> k.length() + v, m)); + put("foo", 4); + put("bar", 5); + put("baz", 6); + }}, updated); + assertSame(m, updated); } @Test - public void invertFlipsKeysAndValues() { - Lens.Simple, Map> invert = ImpureMapLens.invert(); + public void invertedFocusesOnMapWithKeysAndValuesSwitched() { + Lens.Simple, Map> inverted = ImpureMapLens.inverted(); assertEquals(new HashMap() {{ put(1, "foo"); put(2, "bar"); put(3, "baz"); - }}, view(invert, m)); + }}, view(inverted, m)); + Map updated = set(inverted, new HashMap() {{ + put(2, "bar"); + put(3, "baz"); + }}, m); assertEquals(new HashMap() {{ put("bar", 2); put("baz", 3); - }}, set(invert, new HashMap() {{ - put(2, "bar"); - put(3, "baz"); - }}, m)); + }}, updated); + assertSame(m, updated); } } \ No newline at end of file From 66f42156c0754f9d4aa0950d75e5824c2ae42137 Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 24 Jul 2016 13:42:40 -0500 Subject: [PATCH 08/29] Removing pure/impure distinction in Lenses, given any lens composed with asCopy() yields a pure lens. --- ...ollectionLens.java => CollectionLens.java} | 9 +- .../ImpureListLens.java => ListLens.java} | 11 ++- .../palatable/lambda/lens/lenses/MapLens.java | 33 +++++-- .../lens/lenses/impure/ImpureMapLens.java | 55 ----------- ...nLensTest.java => CollectionLensTest.java} | 21 +++- ...ureListLensTest.java => ListLensTest.java} | 19 +++- .../lambda/lens/lenses/MapLensTest.java | 46 +++++---- .../lens/lenses/impure/ImpureMapLensTest.java | 99 ------------------- 8 files changed, 101 insertions(+), 192 deletions(-) rename src/main/java/com/jnape/palatable/lambda/lens/lenses/{impure/ImpureCollectionLens.java => CollectionLens.java} (69%) rename src/main/java/com/jnape/palatable/lambda/lens/lenses/{impure/ImpureListLens.java => ListLens.java} (65%) delete mode 100644 src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLens.java rename src/test/java/com/jnape/palatable/lambda/lens/lenses/{impure/ImpureCollectionLensTest.java => CollectionLensTest.java} (70%) rename src/test/java/com/jnape/palatable/lambda/lens/lenses/{impure/ImpureListLensTest.java => ListLensTest.java} (61%) delete mode 100644 src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLensTest.java diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureCollectionLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/CollectionLens.java similarity index 69% rename from src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureCollectionLens.java rename to src/main/java/com/jnape/palatable/lambda/lens/lenses/CollectionLens.java index dc5b838af..2691e7a1f 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureCollectionLens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/CollectionLens.java @@ -1,5 +1,6 @@ -package com.jnape.palatable.lambda.lens.lenses.impure; +package com.jnape.palatable.lambda.lens.lenses; +import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.lens.Lens; import java.util.Collection; @@ -9,7 +10,11 @@ import static com.jnape.palatable.lambda.lens.Lens.simpleLens; -public class ImpureCollectionLens { +public class CollectionLens { + + public static > Lens.Simple asCopy(Fn1 copyFn) { + return simpleLens(copyFn, (__, copy) -> copy); + } public static > Lens.Simple> asSet() { return simpleLens(HashSet::new, (xsL, xsS) -> { diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureListLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/ListLens.java similarity index 65% rename from src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureListLens.java rename to src/main/java/com/jnape/palatable/lambda/lens/lenses/ListLens.java index 3a3b92793..f03f2cc0b 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureListLens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/ListLens.java @@ -1,14 +1,19 @@ -package com.jnape.palatable.lambda.lens.lenses.impure; +package com.jnape.palatable.lambda.lens.lenses; import com.jnape.palatable.lambda.lens.Lens; +import java.util.ArrayList; import java.util.List; import static com.jnape.palatable.lambda.lens.Lens.simpleLens; -public final class ImpureListLens { +public final class ListLens { - private ImpureListLens() { + private ListLens() { + } + + public static Lens.Simple, List> asCopy() { + return simpleLens(ArrayList::new, (xs, ys) -> ys); } public static Lens.Simple, X> at(int index) { diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java index ae3442830..bfc9d0730 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java @@ -2,16 +2,17 @@ import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.lens.Lens; -import com.jnape.palatable.lambda.lens.lenses.impure.ImpureMapLens; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Set; +import static com.jnape.palatable.lambda.lens.Lens.lens; import static com.jnape.palatable.lambda.lens.Lens.simpleLens; +import static com.jnape.palatable.lambda.lens.functions.View.view; -public class MapLens { +public final class MapLens { private MapLens() { } @@ -21,18 +22,38 @@ public static Lens.Simple, Map> asCopy() { } public static Lens.Simple, V> atKey(K k) { - return ImpureMapLens.atKey(k).compose(asCopy()); + return simpleLens(m -> m.get(k), (m, v) -> { + m.put(k, v); + return m; + }); } public static Lens.Simple, Set> keys() { - return ImpureMapLens.keys().compose(asCopy()); + return simpleLens(Map::keySet, (m, ks) -> { + Set keys = m.keySet(); + keys.retainAll(ks); + ks.removeAll(keys); + ks.forEach(k -> m.put(k, null)); + return m; + }); } public static Lens, Map, Collection, Fn2> values() { - return ImpureMapLens.values().compose(asCopy()); + return lens(Map::values, (m, kvFn) -> { + m.entrySet().forEach(entry -> entry.setValue(kvFn.apply(entry.getKey(), entry.getValue()))); + return m; + }); } public static Lens.Simple, Map> inverted() { - return ImpureMapLens.inverted().compose(asCopy()); + return simpleLens(m -> { + Map inverted = new HashMap<>(); + m.entrySet().forEach(entry -> inverted.put(entry.getValue(), entry.getKey())); + return inverted; + }, (m, im) -> { + m.clear(); + m.putAll(view(inverted(), im)); + return m; + }); } } diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLens.java deleted file mode 100644 index 065e6c334..000000000 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLens.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.jnape.palatable.lambda.lens.lenses.impure; - -import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.lens.Lens; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -import static com.jnape.palatable.lambda.lens.Lens.lens; -import static com.jnape.palatable.lambda.lens.Lens.simpleLens; -import static com.jnape.palatable.lambda.lens.functions.View.view; - -public final class ImpureMapLens { - - private ImpureMapLens() { - } - - public static Lens.Simple, V> atKey(K k) { - return simpleLens(m -> m.get(k), (m, v) -> { - m.put(k, v); - return m; - }); - } - - public static Lens.Simple, Set> keys() { - return simpleLens(Map::keySet, (m, ks) -> { - Set keys = m.keySet(); - keys.retainAll(ks); - ks.removeAll(keys); - ks.forEach(k -> m.put(k, null)); - return m; - }); - } - - public static Lens, Map, Collection, Fn2> values() { - return lens(Map::values, (m, kvFn) -> { - m.entrySet().forEach(entry -> entry.setValue(kvFn.apply(entry.getKey(), entry.getValue()))); - return m; - }); - } - - public static Lens.Simple, Map> inverted() { - return simpleLens(m -> { - Map inverted = new HashMap<>(); - m.entrySet().forEach(entry -> inverted.put(entry.getValue(), entry.getKey())); - return inverted; - }, (m, im) -> { - m.clear(); - m.putAll(view(inverted(), im)); - return m; - }); - } -} diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureCollectionLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/CollectionLensTest.java similarity index 70% rename from src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureCollectionLensTest.java rename to src/test/java/com/jnape/palatable/lambda/lens/lenses/CollectionLensTest.java index 9d8e20097..2df8dd73a 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureCollectionLensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/CollectionLensTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.lens.lenses.impure; +package com.jnape.palatable.lambda.lens.lenses; import com.jnape.palatable.lambda.lens.Lens; import org.junit.Before; @@ -18,8 +18,10 @@ import static java.util.Collections.singleton; import static java.util.stream.Collectors.toList; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; -public class ImpureCollectionLensTest { +public class CollectionLensTest { private List xs; @@ -32,9 +34,20 @@ public void setUp() { }}; } + @Test + public void asCopyUsesMappingFunctionToFocusOnCollectionThroughCopy() { + Lens.Simple, List> asCopy = CollectionLens.asCopy(ArrayList::new); + + assertEquals(xs, view(asCopy, xs)); + assertNotSame(xs, view(asCopy, xs)); + + List updatedList = asList("foo", "bar"); + assertSame(updatedList, set(asCopy, updatedList, xs)); + } + @Test public void asSetFocusesOnCollectionAsSet() { - Lens.Simple, Set> asSet = ImpureCollectionLens.asSet(); + Lens.Simple, Set> asSet = CollectionLens.asSet(); assertEquals(new HashSet<>(xs), view(asSet, xs)); assertEquals(singleton("foo"), view(asSet, asList("foo", "foo"))); @@ -51,7 +64,7 @@ public void asSetFocusesOnCollectionAsSet() { @Test public void asStreamFocusesOnCollectionAsStream() { - Lens.Simple, Stream> asStream = ImpureCollectionLens.asStream(); + Lens.Simple, Stream> asStream = CollectionLens.asStream(); assertEquals(xs, view(asStream, xs).collect(toList())); assertEquals(asList("foo", "bar"), set(asStream, Stream.of("foo", "bar"), xs)); diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureListLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/ListLensTest.java similarity index 61% rename from src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureListLensTest.java rename to src/test/java/com/jnape/palatable/lambda/lens/lenses/ListLensTest.java index 03e7eda39..321d6e802 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureListLensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/ListLensTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.lens.lenses.impure; +package com.jnape.palatable.lambda.lens.lenses; import com.jnape.palatable.lambda.lens.Lens; import org.junit.Before; @@ -12,8 +12,10 @@ import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; -public class ImpureListLensTest { +public class ListLensTest { private List xs; @@ -26,9 +28,20 @@ public void setUp() throws Exception { }}; } + @Test + public void asCopyFocusesOnListThroughCopy() { + Lens.Simple, List> asCopy = ListLens.asCopy(); + + assertEquals(xs, view(asCopy, xs)); + assertNotSame(xs, view(asCopy, xs)); + + List update = asList("foo", "bar", "baz", "quux"); + assertSame(update, set(asCopy, update, xs)); + } + @Test public void atFocusesOnElementAtIndex() { - Lens.Simple, String> at0 = ImpureListLens.at(0); + Lens.Simple, String> at0 = ListLens.at(0); assertEquals("foo", view(at0, xs)); assertEquals(null, view(at0, emptyList())); diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java index 69d7e3f5e..f12bb4dcb 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java @@ -13,26 +13,23 @@ import static com.jnape.palatable.lambda.lens.functions.Set.set; import static com.jnape.palatable.lambda.lens.functions.View.view; +import static com.jnape.palatable.lambda.lens.lenses.MapLens.keys; import static java.util.Arrays.asList; -import static java.util.Collections.singletonMap; -import static java.util.Collections.unmodifiableMap; -import static org.hamcrest.Matchers.containsInAnyOrder; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertThat; public class MapLensTest { private Map m; @Before - public void setUp() throws Exception { - m = unmodifiableMap(new HashMap() {{ + public void setUp() { + m = new HashMap() {{ put("foo", 1); put("bar", 2); put("baz", 3); - }}); + }}; } @Test @@ -45,49 +42,52 @@ public void asCopyFocusesOnMapThroughCopy() { Map update = new HashMap() {{ put("quux", 0); }}; - assertEquals(update, set(asCopy, update, m)); assertSame(update, set(asCopy, update, m)); } @Test public void atKeyFocusesOnValueAtKey() { - Lens.Simple, Integer> atFoo = MapLens.atKey("foo"); + Lens, Map, Integer, Integer> atFoo = MapLens.atKey("foo"); assertEquals((Integer) 1, view(atFoo, m)); + + Map updated = set(atFoo, -1, m); assertEquals(new HashMap() {{ put("foo", -1); put("bar", 2); put("baz", 3); - }}, set(atFoo, -1, m)); + }}, updated); + assertSame(m, updated); } @Test - public void keysFocusesOnKeysWithImmutableSet() { - Lens.Simple, Set> keys = MapLens.keys(); + public void keysFocusesOnKeys() { + Lens, Map, Set, Set> keys = keys(); assertEquals(m.keySet(), view(keys, m)); - view(keys, m).clear(); - assertEquals(new HashSet<>(asList("foo", "bar", "baz")), m.keySet()); + Map updated = set(keys, new HashSet<>(asList("bar", "baz", "quux")), m); assertEquals(new HashMap() {{ put("bar", 2); put("baz", 3); put("quux", null); - }}, set(keys, new HashSet<>(asList("bar", "baz", "quux")), m)); + }}, updated); + assertSame(m, updated); } @Test public void valuesFocusesOnValues() { Lens, Map, Collection, Fn2> values = MapLens.values(); - assertThat(view(values, m), - containsInAnyOrder(m.values().toArray())); + assertEquals(m.values(), view(values, m)); + Map updated = set(values, (k, v) -> k.length() + v, m); assertEquals(new HashMap() {{ put("foo", 4); put("bar", 5); put("baz", 6); - }}, set(values, (k, v) -> k.length() + v, m)); + }}, updated); + assertSame(m, updated); } @Test @@ -100,8 +100,14 @@ public void invertedFocusesOnMapWithKeysAndValuesSwitched() { put(3, "baz"); }}, view(inverted, m)); + Map updated = set(inverted, new HashMap() {{ + put(2, "bar"); + put(3, "baz"); + }}, m); assertEquals(new HashMap() {{ - put("quux", -1); - }}, set(inverted, singletonMap(-1, "quux"), m)); + put("bar", 2); + put("baz", 3); + }}, updated); + assertSame(m, updated); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLensTest.java deleted file mode 100644 index a90c89abc..000000000 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLensTest.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.jnape.palatable.lambda.lens.lenses.impure; - -import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.lens.Lens; -import org.junit.Before; -import org.junit.Test; - -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import static com.jnape.palatable.lambda.lens.functions.Set.set; -import static com.jnape.palatable.lambda.lens.functions.View.view; -import static com.jnape.palatable.lambda.lens.lenses.impure.ImpureMapLens.keys; -import static java.util.Arrays.asList; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertSame; - -public class ImpureMapLensTest { - - private Map m; - - @Before - public void setUp() { - m = new HashMap() {{ - put("foo", 1); - put("bar", 2); - put("baz", 3); - }}; - } - - @Test - public void atKeyFocusesOnValueAtKey() { - Lens, Map, Integer, Integer> atFoo = ImpureMapLens.atKey("foo"); - - assertEquals((Integer) 1, view(atFoo, m)); - - Map updated = set(atFoo, -1, m); - assertEquals(new HashMap() {{ - put("foo", -1); - put("bar", 2); - put("baz", 3); - }}, updated); - assertSame(m, updated); - } - - @Test - public void keysFocusesOnKeys() { - Lens, Map, Set, Set> keys = keys(); - - assertEquals(m.keySet(), view(keys, m)); - - Map updated = set(keys, new HashSet<>(asList("bar", "baz", "quux")), m); - assertEquals(new HashMap() {{ - put("bar", 2); - put("baz", 3); - put("quux", null); - }}, updated); - assertSame(m, updated); - } - - @Test - public void valuesFocusesOnValues() { - Lens, Map, Collection, Fn2> values = ImpureMapLens.values(); - - assertEquals(m.values(), view(values, m)); - - Map updated = set(values, (k, v) -> k.length() + v, m); - assertEquals(new HashMap() {{ - put("foo", 4); - put("bar", 5); - put("baz", 6); - }}, updated); - assertSame(m, updated); - } - - @Test - public void invertedFocusesOnMapWithKeysAndValuesSwitched() { - Lens.Simple, Map> inverted = ImpureMapLens.inverted(); - - assertEquals(new HashMap() {{ - put(1, "foo"); - put(2, "bar"); - put(3, "baz"); - }}, view(inverted, m)); - - Map updated = set(inverted, new HashMap() {{ - put(2, "bar"); - put(3, "baz"); - }}, m); - assertEquals(new HashMap() {{ - put("bar", 2); - put("baz", 3); - }}, updated); - assertSame(m, updated); - } -} \ No newline at end of file From 88c10ce9c9caa02ac4a234c528e9d1b60793eae5 Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 24 Jul 2016 14:54:31 -0500 Subject: [PATCH 09/29] Removing redundant functor tests from Fn2/Fn3, adding missing profunctor test to Fn1; Lens is now a Profunctor --- .../com/jnape/palatable/lambda/lens/Lens.java | 20 ++++++++++++++++++- .../palatable/lambda/functions/Fn1Test.java | 18 +++++++++++++---- .../palatable/lambda/functions/Fn2Test.java | 19 ------------------ .../palatable/lambda/functions/Fn3Test.java | 16 --------------- .../jnape/palatable/lambda/lens/LensTest.java | 18 ++++++++--------- 5 files changed, 41 insertions(+), 50 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java index 4dd5c96e8..fa101863f 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java @@ -3,6 +3,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.Profunctor; import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; import static com.jnape.palatable.lambda.lens.functions.Over.over; @@ -10,7 +11,7 @@ import static com.jnape.palatable.lambda.lens.functions.View.view; @FunctionalInterface -public interface Lens extends Functor { +public interface Lens extends Functor, Profunctor { , FB extends Functor> FT apply(Fn1 fn, S s); @@ -23,6 +24,23 @@ default Lens fmap(Fn1 fn) { return this.compose(Lens.lens(id(), (s, t) -> fn.apply(t))); } + @Override + @SuppressWarnings("unchecked") + default Lens diMapL(Fn1 fn) { + return (Lens) Profunctor.super.diMapL(fn); + } + + @Override + @SuppressWarnings("unchecked") + default Lens diMapR(Fn1 fn) { + return (Lens) Profunctor.super.diMapR(fn); + } + + @Override + default Lens diMap(Fn1 lFn, Fn1 rFn) { + return this.compose(lens(lFn, (r, t) -> rFn.apply(t))); + } + default Lens andThen(Lens f) { return f.compose(this); } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java b/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java index 06ef3d6cf..2bdfca588 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java @@ -1,18 +1,28 @@ package com.jnape.palatable.lambda.functions; +import org.hamcrest.MatcherAssert; import org.junit.Test; -import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertEquals; public class Fn1Test { @Test - public void fmapComposesFunctions() { + public void functorProperties() { Fn1 add2 = integer -> integer + 2; Fn1 toString = Object::toString; - assertThat(add2.fmap(toString).apply(2), is(toString.apply(add2.apply(2)))); + MatcherAssert.assertThat(add2.fmap(toString).apply(2), is(toString.apply(add2.apply(2)))); + } + + @Test + public void profunctorProperties() { + Fn1 add2 = integer -> integer + 2; + + assertEquals((Integer) 3, add2.diMapL(Integer::parseInt).apply("1")); + assertEquals("3", add2.diMapR(Object::toString).apply(1)); + assertEquals("3", add2.diMap(Integer::parseInt, Object::toString).apply("1")); } @Test @@ -20,6 +30,6 @@ public void thenIsJustAnAliasForFmap() { Fn1 add2 = integer -> integer + 2; Fn1 toString = Object::toString; - assertThat(add2.then(toString).apply(2), is(toString.apply(add2.apply(2)))); + MatcherAssert.assertThat(add2.then(toString).apply(2), is(toString.apply(add2.apply(2)))); } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/Fn2Test.java b/src/test/java/com/jnape/palatable/lambda/functions/Fn2Test.java index 12d62c4e2..5a37326f5 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/Fn2Test.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/Fn2Test.java @@ -1,6 +1,5 @@ package com.jnape.palatable.lambda.functions; -import com.jnape.palatable.lambda.functions.builtin.fn1.Id; import org.junit.Test; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; @@ -26,22 +25,4 @@ public void canBePartiallyApplied() { public void uncurries() { assertThat(CHECK_LENGTH.uncurry().apply(tuple("abc", 3)), is(true)); } - - @Test - public void functorProperties() { - assertThat(CHECK_LENGTH.fmap(f -> Id.id()).apply("foo").apply("bar"), is("bar")); - } - - @Test - public void profunctorProperties() { - assertThat(CHECK_LENGTH.diMapL(Object::toString).apply(123).apply(3), is(true)); - assertThat(CHECK_LENGTH.diMapR(fn -> fn.andThen(Object::toString)).apply("123").apply(3), is("true")); - assertThat( - CHECK_LENGTH.>diMap( - Object::toString, - fn -> fn.andThen(Object::toString) - ).apply("123").apply(3), - is("true") - ); - } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/Fn3Test.java b/src/test/java/com/jnape/palatable/lambda/functions/Fn3Test.java index c35532933..7214064f2 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/Fn3Test.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/Fn3Test.java @@ -28,20 +28,4 @@ public void flipsFirstAndSecondArgument() { public void uncurries() { assertThat(CHECK_MULTIPLICATION.uncurry().apply(tuple(2, 3), 6), is(true)); } - - @Test - public void functorProperties() { - assertThat(CHECK_MULTIPLICATION.fmap(f -> f.fmap(g -> g.andThen(Object::toString))).apply(2).apply(3).apply(6), is("true")); - } - - @Test - public void profunctorProperties() { - assertThat(CHECK_MULTIPLICATION.diMapL(Integer::parseInt).apply("2").apply(3).apply(6), is(true)); - assertThat(CHECK_MULTIPLICATION.diMapR(f -> f.fmap(g -> g.andThen(Object::toString))).apply(2).apply(3).apply(6), is("true")); - assertThat(CHECK_MULTIPLICATION.diMap((Fn1) Integer::parseInt, - f -> f.fmap(g -> g.andThen(Object::toString))) - .apply("2") - .apply(3) - .apply(6), is("true")); - } } diff --git a/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java index 84609ba26..9b45928c6 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java @@ -5,6 +5,7 @@ import com.jnape.palatable.lambda.functor.builtin.Identity; import org.junit.Test; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; @@ -25,21 +26,13 @@ public class LensTest { @Test public void setsUnderIdentity() { - Set ints = LENS.>, Identity>apply( - s -> new Identity<>(s.length()), - asList("foo", "bar", "baz") - ).runIdentity(); - + Set ints = LENS.>, Identity>apply(s -> new Identity<>(s.length()), asList("foo", "bar", "baz")).runIdentity(); assertEquals(singleton(3), ints); } @Test public void viewsUnderConst() { - Integer i = LENS.>, Const>apply( - s -> new Const<>(s.length()), - asList("foo", "bar", "baz") - ).runConst(); - + Integer i = LENS.>, Const>apply(s -> new Const<>(s.length()), asList("foo", "bar", "baz")).runConst(); assertEquals((Integer) 3, i); } @@ -59,6 +52,11 @@ public void functorProperties() { assertEquals(false, set(LENS.fmap(Set::isEmpty), 1, singletonList("foo"))); } + @Test + public void profunctorProperties() { + assertEquals(false, set(LENS.diMap(ArrayList::new, Set::isEmpty), 2, singleton("foo"))); + } + @Test public void composition() { Map> map = singletonMap("foo", asList("one", "two", "three")); From bd3dd5280db79c739bc2c71de50765f9061fedff Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 10 Jul 2016 00:15:14 -0500 Subject: [PATCH 10/29] Initial lens support with Const and Identity functor to support view/set/over --- .../lambda/functor/builtin/Const.java | 42 ++++++++++++++++ .../lambda/functor/builtin/Identity.java | 32 ++++++++++++ .../com/jnape/palatable/lambda/lens/Lens.java | 40 +++++++++++++++ .../palatable/lambda/lens/functions/Over.java | 38 ++++++++++++++ .../palatable/lambda/lens/functions/Set.java | 38 ++++++++++++++ .../palatable/lambda/lens/functions/View.java | 29 +++++++++++ .../lambda/functor/builtin/ConstTest.java | 18 +++++++ .../lambda/functor/builtin/IdentityTest.java | 13 +++++ .../jnape/palatable/lambda/lens/LensTest.java | 50 +++++++++++++++++++ .../lambda/lens/functions/OverTest.java | 22 ++++++++ .../lambda/lens/functions/SetTest.java | 21 ++++++++ .../lambda/lens/functions/ViewTest.java | 22 ++++++++ 12 files changed, 365 insertions(+) create mode 100644 src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java create mode 100644 src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java create mode 100644 src/main/java/com/jnape/palatable/lambda/lens/Lens.java create mode 100644 src/main/java/com/jnape/palatable/lambda/lens/functions/Over.java create mode 100644 src/main/java/com/jnape/palatable/lambda/lens/functions/Set.java create mode 100644 src/main/java/com/jnape/palatable/lambda/lens/functions/View.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functor/builtin/ConstTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functor/builtin/IdentityTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/lens/LensTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/lens/functions/OverTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/lens/functions/SetTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/lens/functions/ViewTest.java diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java new file mode 100644 index 000000000..e04739e46 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java @@ -0,0 +1,42 @@ +package com.jnape.palatable.lambda.functor.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.functor.Functor; + +public final class Const implements Functor, Bifunctor { + + private final A a; + + public Const(A a) { + this.a = a; + } + + public A runConst() { + return a; + } + + @Override + @SuppressWarnings("unchecked") + public Const fmap(Fn1 fn) { + return (Const) this; + } + + @Override + @SuppressWarnings("unchecked") + public Const biMapL(Fn1 fn) { + return (Const) Bifunctor.super.biMapL(fn); + } + + @Override + @SuppressWarnings("unchecked") + public Const biMapR(Fn1 fn) { + return (Const) Bifunctor.super.biMapR(fn); + } + + @Override + public Const biMap(Fn1 lFn, + Fn1 rFn) { + return new Const<>(lFn.apply(a)); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java new file mode 100644 index 000000000..c0e2db683 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java @@ -0,0 +1,32 @@ +package com.jnape.palatable.lambda.functor.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.Functor; + +public class Identity implements Functor { + + private final A a; + + public Identity(A a) { + this.a = a; + } + + public A runIdentity() { + return a; + } + + @Override + public Identity fmap(Fn1 fn) { + return new Identity<>(fn.apply(a)); + } + + @Override + public boolean equals(Object other) { + return other instanceof Identity && a.equals(((Identity) other).a); + } + + @Override + public int hashCode() { + return a.hashCode(); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java new file mode 100644 index 000000000..c015b5cce --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java @@ -0,0 +1,40 @@ +package com.jnape.palatable.lambda.lens; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functor.Functor; + +@FunctionalInterface +public interface Lens { + + , FB extends Functor> FT apply(Fn1 fn, S s); + + default , FB extends Functor> Fixed fix() { + return this::apply; + } + + static Lens lens(Fn1 getter, + Fn2 setter) { + return new Lens() { + @Override + @SuppressWarnings("unchecked") + public , FB extends Functor> FT apply(Fn1 fn, + S s) { + return (FT) fn.apply(getter.apply(s)).fmap(setter.apply(s)); + } + }; + } + + @SuppressWarnings("unchecked") + static Lens.Simple simpleLens(Fn1 getter, + Fn2 setter) { + return lens(getter, setter)::apply; + } + + interface Simple extends Lens { + } + + interface Fixed, FB extends Functor> + extends Fn2, S, FT> { + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/lens/functions/Over.java b/src/main/java/com/jnape/palatable/lambda/lens/functions/Over.java new file mode 100644 index 000000000..4ee24be65 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/lens/functions/Over.java @@ -0,0 +1,38 @@ +package com.jnape.palatable.lambda.lens.functions; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functor.builtin.Identity; +import com.jnape.palatable.lambda.lens.Lens; + +public final class Over implements Fn3, Fn1, S, T> { + + private Over() { + } + + @Override + public T apply(Lens lens, Fn1 fn, S s) { + return lens., Identity>fix() + .apply(fn.fmap((Fn1>) Identity::new), s) + .runIdentity(); + } + + public static Over over() { + return new Over<>(); + } + + public static Fn2, S, T> over( + Lens lens) { + return Over.over().apply(lens); + } + + public static Fn1 over(Lens lens, + Fn1 fn) { + return over(lens).apply(fn); + } + + public static T over(Lens lens, Fn1 fn, S s) { + return over(lens, fn).apply(s); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/lens/functions/Set.java b/src/main/java/com/jnape/palatable/lambda/lens/functions/Set.java new file mode 100644 index 000000000..ad2df38ac --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/lens/functions/Set.java @@ -0,0 +1,38 @@ +package com.jnape.palatable.lambda.lens.functions; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functor.builtin.Identity; +import com.jnape.palatable.lambda.lens.Lens; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; + +public final class Set implements Fn3, B, S, T> { + + private Set() { + } + + @Override + public T apply(Lens lens, B b, S s) { + return lens., Identity>fix() + .apply(constantly(b).fmap(Identity::new), s) + .runIdentity(); + } + + public static Set set() { + return new Set<>(); + } + + public static Fn2 set(Lens lens) { + return Set.set().apply(lens); + } + + public static Fn1 set(Lens lens, B b) { + return set(lens).apply(b); + } + + public static T set(Lens lens, B b, S s) { + return set(lens, b).apply(s); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/lens/functions/View.java b/src/main/java/com/jnape/palatable/lambda/lens/functions/View.java new file mode 100644 index 000000000..1d12df267 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/lens/functions/View.java @@ -0,0 +1,29 @@ +package com.jnape.palatable.lambda.lens.functions; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functor.builtin.Const; +import com.jnape.palatable.lambda.lens.Lens; + +public final class View implements Fn2, S, A> { + + private View() { + } + + @Override + public A apply(Lens lens, S s) { + return lens., Const>fix().apply(Const::new, s).runConst(); + } + + public static View view() { + return new View<>(); + } + + public static Fn1 view(Lens lens) { + return View.view().apply(lens); + } + + public static A view(Lens lens, S s) { + return view(lens).apply(s); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/functor/builtin/ConstTest.java b/src/test/java/com/jnape/palatable/lambda/functor/builtin/ConstTest.java new file mode 100644 index 000000000..5d8a06abc --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functor/builtin/ConstTest.java @@ -0,0 +1,18 @@ +package com.jnape.palatable.lambda.functor.builtin; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class ConstTest { + + @Test + public void functorProperties() { + assertEquals("foo", new Const("foo").fmap(x -> x + 1).runConst()); + } + + @Test + public void bifunctorProperties() { + assertEquals("FOO", new Const("foo").biMap(String::toUpperCase, x -> x + 1).runConst()); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functor/builtin/IdentityTest.java b/src/test/java/com/jnape/palatable/lambda/functor/builtin/IdentityTest.java new file mode 100644 index 000000000..792980e99 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functor/builtin/IdentityTest.java @@ -0,0 +1,13 @@ +package com.jnape.palatable.lambda.functor.builtin; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class IdentityTest { + + @Test + public void functorProperties() { + assertEquals("FOO", new Identity<>("foo").fmap(String::toUpperCase).runIdentity()); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java new file mode 100644 index 000000000..f1605e7ad --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java @@ -0,0 +1,50 @@ +package com.jnape.palatable.lambda.lens; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.builtin.Const; +import com.jnape.palatable.lambda.functor.builtin.Identity; +import org.junit.Test; + +import java.util.List; +import java.util.Set; + +import static java.util.Arrays.asList; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertEquals; + +public class LensTest { + + private static final Lens, Set, String, Integer> LENS = Lens.lens(xs -> xs.get(0), (xs, i) -> singleton(i)); + + @Test + public void setsUnderIdentity() { + Set ints = LENS.>, Identity>apply( + s -> new Identity<>(s.length()), + asList("foo", "bar", "baz") + ).runIdentity(); + + assertEquals(singleton(3), ints); + } + + @Test + public void viewsUnderConst() { + Integer i = LENS.>, Const>apply( + s -> new Const<>(s.length()), + asList("foo", "bar", "baz") + ).runConst(); + + assertEquals((Integer) 3, i); + } + + @Test + public void fix() { + Fn1> fn = s -> new Const<>(s.length()); + List s = singletonList("foo"); + + Integer fixedLensResult = LENS.>, Const>fix().apply(fn, s).runConst(); + Integer unfixedLensResult = LENS.>, Const>apply(fn, s).runConst(); + + assertEquals(unfixedLensResult, fixedLensResult); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/functions/OverTest.java b/src/test/java/com/jnape/palatable/lambda/lens/functions/OverTest.java new file mode 100644 index 000000000..e5a95cf4a --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/lens/functions/OverTest.java @@ -0,0 +1,22 @@ +package com.jnape.palatable.lambda.lens.functions; + +import com.jnape.palatable.lambda.lens.Lens; +import org.junit.Test; + +import java.util.List; +import java.util.Set; + +import static com.jnape.palatable.lambda.lens.Lens.lens; +import static com.jnape.palatable.lambda.lens.functions.Over.over; +import static java.util.Arrays.asList; +import static java.util.Collections.singleton; +import static org.junit.Assert.assertEquals; + +public class OverTest { + + @Test + public void mapsDataWithLensAndFunction() { + Lens, Set, String, Integer> lens = lens(xs -> xs.get(0), (xs, i) -> singleton(i)); + assertEquals(singleton(1), over(lens, String::length, asList("a", "aa", "aaa"))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/functions/SetTest.java b/src/test/java/com/jnape/palatable/lambda/lens/functions/SetTest.java new file mode 100644 index 000000000..47fa8a959 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/lens/functions/SetTest.java @@ -0,0 +1,21 @@ +package com.jnape.palatable.lambda.lens.functions; + +import com.jnape.palatable.lambda.lens.Lens; +import org.junit.Test; + +import java.util.List; + +import static com.jnape.palatable.lambda.lens.Lens.lens; +import static com.jnape.palatable.lambda.lens.functions.Set.set; +import static java.util.Arrays.asList; +import static java.util.Collections.singleton; +import static org.junit.Assert.assertEquals; + +public class SetTest { + + @Test + public void updatesWithLensAndNewValue() { + Lens, java.util.Set, String, Integer> lens = lens(xs -> xs.get(0), (xs, i) -> singleton(i)); + assertEquals(singleton(5), set(lens, 5, asList("a", "aa", "aaa"))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/functions/ViewTest.java b/src/test/java/com/jnape/palatable/lambda/lens/functions/ViewTest.java new file mode 100644 index 000000000..201ec990a --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/lens/functions/ViewTest.java @@ -0,0 +1,22 @@ +package com.jnape.palatable.lambda.lens.functions; + +import com.jnape.palatable.lambda.lens.Lens; +import org.junit.Test; + +import java.util.List; +import java.util.Set; + +import static com.jnape.palatable.lambda.lens.Lens.lens; +import static com.jnape.palatable.lambda.lens.functions.View.view; +import static java.util.Arrays.asList; +import static java.util.Collections.singleton; +import static org.junit.Assert.assertEquals; + +public class ViewTest { + + @Test + public void viewsSubPartWithLens() { + Lens, Set, String, Integer> lens = lens(xs -> xs.get(0), (xs, i) -> singleton(i)); + assertEquals("foo", view(lens, asList("foo", "bar", "baz"))); + } +} \ No newline at end of file From 5344286d3aa05516c0d612c6bd452f301f17d570 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 16 Jul 2016 16:32:25 -0500 Subject: [PATCH 11/29] Adding lens composition and initial unsafe map lenses --- pom.xml | 10 ++- .../com/jnape/palatable/lambda/lens/Lens.java | 20 ++++- .../lambda/lens/lenses/UnsafeMapLens.java | 55 ++++++++++++ .../jnape/palatable/lambda/lens/LensTest.java | 27 +++++- .../lambda/lens/lenses/UnsafeMapLensTest.java | 86 +++++++++++++++++++ 5 files changed, 195 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/lens/lenses/UnsafeMapLens.java create mode 100644 src/test/java/com/jnape/palatable/lambda/lens/lenses/UnsafeMapLensTest.java diff --git a/pom.xml b/pom.xml index 7a416f475..cc95e982a 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,6 @@ - + 4.0.0 @@ -56,6 +57,7 @@ 3.1 1.0 3.3 + 1.3 @@ -63,6 +65,12 @@ junit junit + + org.hamcrest + hamcrest-all + ${hamcrest-all.version} + test + org.mockito mockito-all diff --git a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java index c015b5cce..830c00057 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java @@ -4,8 +4,13 @@ import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.functor.Functor; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.lens.functions.Over.over; +import static com.jnape.palatable.lambda.lens.functions.Set.set; +import static com.jnape.palatable.lambda.lens.functions.View.view; + @FunctionalInterface -public interface Lens { +public interface Lens extends Functor { , FB extends Functor> FT apply(Fn1 fn, S s); @@ -13,6 +18,19 @@ default , FB extends Functor> Fixed return this::apply; } + @Override + default Lens fmap(Fn1 fn) { + return this.compose(Lens.lens(id(), (s, t) -> fn.apply(t))); + } + + default Lens andThen(Lens f) { + return f.compose(this); + } + + default Lens compose(Lens g) { + return lens(view(g).fmap(view(this)), (q, b) -> over(g, set(this, b), q)); + } + static Lens lens(Fn1 getter, Fn2 setter) { return new Lens() { diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/UnsafeMapLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/UnsafeMapLens.java new file mode 100644 index 000000000..8d57de8af --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/UnsafeMapLens.java @@ -0,0 +1,55 @@ +package com.jnape.palatable.lambda.lens.lenses; + +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.lens.Lens; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import static com.jnape.palatable.lambda.lens.Lens.lens; +import static com.jnape.palatable.lambda.lens.Lens.simpleLens; +import static com.jnape.palatable.lambda.lens.functions.View.view; + +public final class UnsafeMapLens { + + private UnsafeMapLens() { + } + + public static Lens.Simple, V> atKey(K k) { + return simpleLens(m -> m.get(k), (m, v) -> { + m.put(k, v); + return m; + }); + } + + public static Lens.Simple, Set> keys() { + return simpleLens(Map::keySet, (m, ks) -> { + Set keys = m.keySet(); + keys.retainAll(ks); + ks.removeAll(keys); + ks.forEach(k -> m.put(k, null)); + return m; + }); + } + + public static Lens, Map, Collection, Fn2> values() { + return lens(Map::values, (m, kvFn) -> { + m.entrySet().forEach(entry -> entry.setValue(kvFn.apply(entry.getKey(), entry.getValue()))); + return m; + }); + } + + public static Lens.Simple, Map> invert() { + return simpleLens(m -> { + Map inverted = new HashMap<>(); + m.entrySet().forEach(entry -> inverted.put(entry.getValue(), entry.getKey())); + return inverted; + }, (m, im) -> { + m.clear(); + m.putAll(view(invert(), im)); + return m; + }); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java index f1605e7ad..84609ba26 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java @@ -6,16 +6,22 @@ import org.junit.Test; import java.util.List; +import java.util.Map; import java.util.Set; +import static com.jnape.palatable.lambda.lens.Lens.lens; +import static com.jnape.palatable.lambda.lens.functions.Set.set; +import static com.jnape.palatable.lambda.lens.functions.View.view; import static java.util.Arrays.asList; import static java.util.Collections.singleton; import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; import static org.junit.Assert.assertEquals; public class LensTest { - private static final Lens, Set, String, Integer> LENS = Lens.lens(xs -> xs.get(0), (xs, i) -> singleton(i)); + private static final Lens>, Map>, List, Set> EARLIER_LENS = lens(m -> m.get("foo"), (m, s) -> singletonMap("foo", s)); + private static final Lens, Set, String, Integer> LENS = lens(xs -> xs.get(0), (xs, i) -> singleton(i)); @Test public void setsUnderIdentity() { @@ -47,4 +53,23 @@ public void fix() { assertEquals(unfixedLensResult, fixedLensResult); } + + @Test + public void functorProperties() { + assertEquals(false, set(LENS.fmap(Set::isEmpty), 1, singletonList("foo"))); + } + + @Test + public void composition() { + Map> map = singletonMap("foo", asList("one", "two", "three")); + assertEquals("one", view(LENS.compose(EARLIER_LENS), map)); + assertEquals(singletonMap("foo", singleton(1)), set(LENS.compose(EARLIER_LENS), 1, map)); + } + + @Test + public void andThenComposesInReverse() { + Map> map = singletonMap("foo", asList("one", "two", "three")); + assertEquals("one", view(EARLIER_LENS.andThen(LENS), map)); + assertEquals(singletonMap("foo", singleton(1)), set(EARLIER_LENS.andThen(LENS), 1, map)); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/UnsafeMapLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/UnsafeMapLensTest.java new file mode 100644 index 000000000..d1dceac4b --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/UnsafeMapLensTest.java @@ -0,0 +1,86 @@ +package com.jnape.palatable.lambda.lens.lenses; + +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.lens.Lens; +import org.junit.Before; +import org.junit.Test; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static com.jnape.palatable.lambda.lens.functions.Over.over; +import static com.jnape.palatable.lambda.lens.functions.Set.set; +import static com.jnape.palatable.lambda.lens.functions.View.view; +import static com.jnape.palatable.lambda.lens.lenses.UnsafeMapLens.keys; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; + +public class UnsafeMapLensTest { + + private Map m; + + @Before + public void setUp() { + m = new HashMap() {{ + put("foo", 1); + put("bar", 2); + put("baz", 3); + }}; + } + + @Test + public void atKeyFocusesOnValueAtKey() { + Lens, Map, Integer, Integer> atFoo = UnsafeMapLens.atKey("foo"); + + assertEquals((Integer) 1, view(atFoo, m)); + assertEquals((Integer) (-1), view(atFoo, set(atFoo, -1, m))); + assertEquals((Integer) (-2), view(atFoo, over(atFoo, x -> x * 2, m))); + } + + @Test + public void keysFocusOnKeys() { + Lens, Map, Set, Set> keys = keys(); + + assertEquals(m.keySet(), view(keys, m)); + assertEquals(new HashMap() {{ + put("bar", 2); + put("baz", 3); + put("quux", null); + }}, set(keys, new HashSet<>(asList("bar", "baz", "quux")), m)); + } + + @Test + public void valuesFocusOnValues() { + Lens, Map, Collection, Fn2> values = UnsafeMapLens.values(); + + assertEquals(m.values(), view(values, m)); + assertEquals(new HashMap() {{ + put("foo", 4); + put("bar", 5); + put("baz", 6); + }}, + set(values, (k, v) -> k.length() + v, m)); + } + + @Test + public void invertFlipsKeysAndValues() { + Lens.Simple, Map> invert = UnsafeMapLens.invert(); + + assertEquals(new HashMap() {{ + put(1, "foo"); + put(2, "bar"); + put(3, "baz"); + }}, view(invert, m)); + + assertEquals(new HashMap() {{ + put("bar", 2); + put("baz", 3); + }}, set(invert, new HashMap() {{ + put(2, "bar"); + put(3, "baz"); + }}, m)); + } +} \ No newline at end of file From df65567eb85f2f4eda706c832bba277b1cded050 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 16 Jul 2016 17:01:43 -0500 Subject: [PATCH 12/29] Adding Either#toOptional --- .../com/jnape/palatable/lambda/adt/Either.java | 4 ++++ .../jnape/palatable/lambda/adt/EitherTest.java | 15 +++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/adt/Either.java b/src/main/java/com/jnape/palatable/lambda/adt/Either.java index 4fdd106fd..e4dda8c38 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/Either.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/Either.java @@ -216,6 +216,10 @@ public static Either fromOptional(Optional optional, Supplier .orElse(left(leftFn.get())); } + public Optional toOptional() { + return match(__ -> Optional.empty(), Optional::ofNullable); + } + /** * Attempt to execute the {@link CheckedSupplier}, returning its result in a right value. If the supplier throws an * exception, apply leftFn to it, wrap it in a left value and return it. diff --git a/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java b/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java index 634706372..4e2911dc7 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java @@ -116,13 +116,20 @@ public void matchDuallyLiftsAndFlattens() { assertThat(right.match(l -> l + "bar", r -> r + 1), is(2)); } + @Test + public void toOptionalMapsEitherToOptional() { + assertEquals(Optional.of(1), Either.right(1).toOptional()); + assertEquals(Optional.empty(), Either.right(null).toOptional()); + assertEquals(Optional.empty(), Either.left("fail").toOptional()); + } + @Test public void fromOptionalMapsOptionalToEither() { - Optional present = Optional.of("foo"); - Optional absent = Optional.empty(); + Optional present = Optional.of(1); + Optional absent = Optional.empty(); - assertThat(fromOptional(present, () -> -1), is(right("foo"))); - assertThat(fromOptional(absent, () -> -1), is(left(-1))); + assertThat(fromOptional(present, () -> "fail"), is(right(1))); + assertThat(fromOptional(absent, () -> "fail"), is(left("fail"))); } @Test From 7c1e74b72c5f766cacca52ad3c1f0028de3e8abd Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 16 Jul 2016 18:16:47 -0500 Subject: [PATCH 13/29] Adding impure list and collection lenses; renaming UnsafeMapLens to ImpureMapLens --- .../lens/lenses/ImpureCollectionLens.java | 28 +++++++++ .../lambda/lens/lenses/ImpureListLens.java | 22 +++++++ ...{UnsafeMapLens.java => ImpureMapLens.java} | 4 +- .../lens/lenses/ImpureCollectionLensTest.java | 59 +++++++++++++++++++ .../lens/lenses/ImpureListLensTest.java | 38 ++++++++++++ ...apLensTest.java => ImpureMapLensTest.java} | 10 ++-- 6 files changed, 154 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureCollectionLens.java create mode 100644 src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureListLens.java rename src/main/java/com/jnape/palatable/lambda/lens/lenses/{UnsafeMapLens.java => ImpureMapLens.java} (96%) create mode 100644 src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureCollectionLensTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureListLensTest.java rename src/test/java/com/jnape/palatable/lambda/lens/lenses/{UnsafeMapLensTest.java => ImpureMapLensTest.java} (91%) diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureCollectionLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureCollectionLens.java new file mode 100644 index 000000000..694df1c01 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureCollectionLens.java @@ -0,0 +1,28 @@ +package com.jnape.palatable.lambda.lens.lenses; + +import com.jnape.palatable.lambda.lens.Lens; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Stream; + +import static com.jnape.palatable.lambda.lens.Lens.simpleLens; + +public class ImpureCollectionLens { + + public static > Lens.Simple> asSet() { + return simpleLens(HashSet::new, (xsL, xsS) -> { + xsL.retainAll(xsS); + return xsL; + }); + } + + public static > Lens.Simple> asStream() { + return simpleLens(Collection::stream, (xsL, xsS) -> { + xsL.clear(); + xsS.forEach(xsL::add); + return xsL; + }); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureListLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureListLens.java new file mode 100644 index 000000000..8b34380d6 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureListLens.java @@ -0,0 +1,22 @@ +package com.jnape.palatable.lambda.lens.lenses; + +import com.jnape.palatable.lambda.lens.Lens; + +import java.util.List; + +import static com.jnape.palatable.lambda.lens.Lens.simpleLens; + +public final class ImpureListLens { + + private ImpureListLens() { + } + + public static Lens.Simple, X> at(int index) { + return simpleLens(xs -> xs.size() > index ? xs.get(index) : null, + (xs, x) -> { + if (xs.size() > index) + xs.set(index, x); + return xs; + }); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/UnsafeMapLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureMapLens.java similarity index 96% rename from src/main/java/com/jnape/palatable/lambda/lens/lenses/UnsafeMapLens.java rename to src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureMapLens.java index 8d57de8af..75db1c09c 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/UnsafeMapLens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureMapLens.java @@ -12,9 +12,9 @@ import static com.jnape.palatable.lambda.lens.Lens.simpleLens; import static com.jnape.palatable.lambda.lens.functions.View.view; -public final class UnsafeMapLens { +public final class ImpureMapLens { - private UnsafeMapLens() { + private ImpureMapLens() { } public static Lens.Simple, V> atKey(K k) { diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureCollectionLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureCollectionLensTest.java new file mode 100644 index 000000000..2bd72995d --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureCollectionLensTest.java @@ -0,0 +1,59 @@ +package com.jnape.palatable.lambda.lens.lenses; + +import com.jnape.palatable.lambda.lens.Lens; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + +import static com.jnape.palatable.lambda.lens.functions.Set.set; +import static com.jnape.palatable.lambda.lens.functions.View.view; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; +import static java.util.stream.Collectors.toList; +import static org.junit.Assert.assertEquals; + +public class ImpureCollectionLensTest { + + private List xs; + + @Before + public void setUp() { + xs = new ArrayList() {{ + add("foo"); + add("bar"); + add("baz"); + }}; + } + + @Test + public void asSetFocusesOnCollectionAsSet() { + Lens.Simple, Set> asSet = ImpureCollectionLens.asSet(); + + assertEquals(new HashSet<>(xs), view(asSet, xs)); + assertEquals(singleton("foo"), view(asSet, asList("foo", "foo"))); + assertEquals(emptySet(), view(asSet, emptyList())); + + assertEquals(asList("foo", "bar"), set(asSet, new HashSet<>(asList("foo", "bar")), xs)); + assertEquals(asList("foo", "foo", "bar"), + set(asSet, + new HashSet<>(asList("foo", "bar")), + new ArrayList<>(asList("foo", "foo", "bar", "baz")))); + assertEquals(emptyList(), set(asSet, emptySet(), xs)); + assertEquals(emptyList(), set(asSet, singleton("foo"), emptyList())); + } + + @Test + public void asStreamFocusesOnCollectionAsStream() { + Lens.Simple, Stream> asStream = ImpureCollectionLens.asStream(); + + assertEquals(xs, view(asStream, xs).collect(toList())); + assertEquals(asList("foo", "bar"), set(asStream, Stream.of("foo", "bar"), xs)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureListLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureListLensTest.java new file mode 100644 index 000000000..f908dfe3c --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureListLensTest.java @@ -0,0 +1,38 @@ +package com.jnape.palatable.lambda.lens.lenses; + +import com.jnape.palatable.lambda.lens.Lens; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static com.jnape.palatable.lambda.lens.functions.Set.set; +import static com.jnape.palatable.lambda.lens.functions.View.view; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static org.junit.Assert.assertEquals; + +public class ImpureListLensTest { + + private List xs; + + @Before + public void setUp() throws Exception { + xs = new ArrayList() {{ + add("foo"); + add("bar"); + add("baz"); + }}; + } + + @Test + public void atFocusesOnElementAtIndex() { + Lens.Simple, String> at0 = ImpureListLens.at(0); + + assertEquals("foo", view(at0, xs)); + assertEquals(null, view(at0, emptyList())); + assertEquals(asList("quux", "bar", "baz"), set(at0, "quux", xs)); + assertEquals(emptyList(), set(at0, "quux", emptyList())); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/UnsafeMapLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureMapLensTest.java similarity index 91% rename from src/test/java/com/jnape/palatable/lambda/lens/lenses/UnsafeMapLensTest.java rename to src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureMapLensTest.java index d1dceac4b..247c1df79 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/UnsafeMapLensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureMapLensTest.java @@ -14,11 +14,11 @@ import static com.jnape.palatable.lambda.lens.functions.Over.over; import static com.jnape.palatable.lambda.lens.functions.Set.set; import static com.jnape.palatable.lambda.lens.functions.View.view; -import static com.jnape.palatable.lambda.lens.lenses.UnsafeMapLens.keys; +import static com.jnape.palatable.lambda.lens.lenses.ImpureMapLens.keys; import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; -public class UnsafeMapLensTest { +public class ImpureMapLensTest { private Map m; @@ -33,7 +33,7 @@ public void setUp() { @Test public void atKeyFocusesOnValueAtKey() { - Lens, Map, Integer, Integer> atFoo = UnsafeMapLens.atKey("foo"); + Lens, Map, Integer, Integer> atFoo = ImpureMapLens.atKey("foo"); assertEquals((Integer) 1, view(atFoo, m)); assertEquals((Integer) (-1), view(atFoo, set(atFoo, -1, m))); @@ -54,7 +54,7 @@ public void keysFocusOnKeys() { @Test public void valuesFocusOnValues() { - Lens, Map, Collection, Fn2> values = UnsafeMapLens.values(); + Lens, Map, Collection, Fn2> values = ImpureMapLens.values(); assertEquals(m.values(), view(values, m)); assertEquals(new HashMap() {{ @@ -67,7 +67,7 @@ public void valuesFocusOnValues() { @Test public void invertFlipsKeysAndValues() { - Lens.Simple, Map> invert = UnsafeMapLens.invert(); + Lens.Simple, Map> invert = ImpureMapLens.invert(); assertEquals(new HashMap() {{ put(1, "foo"); From 01b117a41c9501e63a3128f4bec369dbcce16721 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 16 Jul 2016 18:18:20 -0500 Subject: [PATCH 14/29] moving impure lenses to impure package of shame --- .../lambda/lens/lenses/{ => impure}/ImpureCollectionLens.java | 2 +- .../lambda/lens/lenses/{ => impure}/ImpureListLens.java | 2 +- .../lambda/lens/lenses/{ => impure}/ImpureMapLens.java | 2 +- .../lens/lenses/{ => impure}/ImpureCollectionLensTest.java | 2 +- .../lambda/lens/lenses/{ => impure}/ImpureListLensTest.java | 2 +- .../lambda/lens/lenses/{ => impure}/ImpureMapLensTest.java | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) rename src/main/java/com/jnape/palatable/lambda/lens/lenses/{ => impure}/ImpureCollectionLens.java (92%) rename src/main/java/com/jnape/palatable/lambda/lens/lenses/{ => impure}/ImpureListLens.java (91%) rename src/main/java/com/jnape/palatable/lambda/lens/lenses/{ => impure}/ImpureMapLens.java (96%) rename src/test/java/com/jnape/palatable/lambda/lens/lenses/{ => impure}/ImpureCollectionLensTest.java (97%) rename src/test/java/com/jnape/palatable/lambda/lens/lenses/{ => impure}/ImpureListLensTest.java (94%) rename src/test/java/com/jnape/palatable/lambda/lens/lenses/{ => impure}/ImpureMapLensTest.java (95%) diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureCollectionLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureCollectionLens.java similarity index 92% rename from src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureCollectionLens.java rename to src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureCollectionLens.java index 694df1c01..dc5b838af 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureCollectionLens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureCollectionLens.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.lens.lenses; +package com.jnape.palatable.lambda.lens.lenses.impure; import com.jnape.palatable.lambda.lens.Lens; diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureListLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureListLens.java similarity index 91% rename from src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureListLens.java rename to src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureListLens.java index 8b34380d6..3a3b92793 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureListLens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureListLens.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.lens.lenses; +package com.jnape.palatable.lambda.lens.lenses.impure; import com.jnape.palatable.lambda.lens.Lens; diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureMapLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLens.java similarity index 96% rename from src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureMapLens.java rename to src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLens.java index 75db1c09c..298b3330f 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/ImpureMapLens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLens.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.lens.lenses; +package com.jnape.palatable.lambda.lens.lenses.impure; import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.lens.Lens; diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureCollectionLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureCollectionLensTest.java similarity index 97% rename from src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureCollectionLensTest.java rename to src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureCollectionLensTest.java index 2bd72995d..9d8e20097 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureCollectionLensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureCollectionLensTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.lens.lenses; +package com.jnape.palatable.lambda.lens.lenses.impure; import com.jnape.palatable.lambda.lens.Lens; import org.junit.Before; diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureListLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureListLensTest.java similarity index 94% rename from src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureListLensTest.java rename to src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureListLensTest.java index f908dfe3c..03e7eda39 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureListLensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureListLensTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.lens.lenses; +package com.jnape.palatable.lambda.lens.lenses.impure; import com.jnape.palatable.lambda.lens.Lens; import org.junit.Before; diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureMapLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLensTest.java similarity index 95% rename from src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureMapLensTest.java rename to src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLensTest.java index 247c1df79..de0ee33a3 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/ImpureMapLensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLensTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.lens.lenses; +package com.jnape.palatable.lambda.lens.lenses.impure; import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.lens.Lens; @@ -14,7 +14,7 @@ import static com.jnape.palatable.lambda.lens.functions.Over.over; import static com.jnape.palatable.lambda.lens.functions.Set.set; import static com.jnape.palatable.lambda.lens.functions.View.view; -import static com.jnape.palatable.lambda.lens.lenses.ImpureMapLens.keys; +import static com.jnape.palatable.lambda.lens.lenses.impure.ImpureMapLens.keys; import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; From aaec665da7a46fc47224c2908cb2effe221732eb Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 16 Jul 2016 18:48:44 -0500 Subject: [PATCH 15/29] adding Optional and Either lenses --- .../lambda/lens/lenses/EitherLens.java | 23 +++++++++++ .../lambda/lens/lenses/OptionalLens.java | 17 ++++++++ .../lambda/lens/lenses/EitherLensTest.java | 40 +++++++++++++++++++ .../lambda/lens/lenses/OptionalLensTest.java | 23 +++++++++++ 4 files changed, 103 insertions(+) create mode 100644 src/main/java/com/jnape/palatable/lambda/lens/lenses/EitherLens.java create mode 100644 src/main/java/com/jnape/palatable/lambda/lens/lenses/OptionalLens.java create mode 100644 src/test/java/com/jnape/palatable/lambda/lens/lenses/EitherLensTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/lens/lenses/OptionalLensTest.java diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/EitherLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/EitherLens.java new file mode 100644 index 000000000..cb92c6dda --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/EitherLens.java @@ -0,0 +1,23 @@ +package com.jnape.palatable.lambda.lens.lenses; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.lens.Lens; + +import java.util.Optional; + +import static com.jnape.palatable.lambda.lens.Lens.simpleLens; + +public final class EitherLens { + + private EitherLens() { + } + + public static Lens.Simple, Optional> right() { + return simpleLens(Either::toOptional, (lOrR, optR) -> optR.>map(Either::right).orElse(lOrR)); + } + + public static Lens.Simple, Optional> left() { + return simpleLens(e -> e.match(Optional::ofNullable, __ -> Optional.empty()), + (lOrR, optL) -> optL.>map(Either::left).orElse(lOrR)); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/OptionalLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/OptionalLens.java new file mode 100644 index 000000000..88b562574 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/OptionalLens.java @@ -0,0 +1,17 @@ +package com.jnape.palatable.lambda.lens.lenses; + +import com.jnape.palatable.lambda.lens.Lens; + +import java.util.Optional; + +import static com.jnape.palatable.lambda.lens.Lens.simpleLens; + +public final class OptionalLens { + + private OptionalLens() { + } + + public static Lens.Simple> asOptional() { + return simpleLens(Optional::ofNullable, (v, optV) -> optV.orElse(v)); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/EitherLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/EitherLensTest.java new file mode 100644 index 000000000..fb518dd9b --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/EitherLensTest.java @@ -0,0 +1,40 @@ +package com.jnape.palatable.lambda.lens.lenses; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.lens.Lens; +import org.junit.Test; + +import java.util.Optional; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.lens.functions.Set.set; +import static com.jnape.palatable.lambda.lens.functions.View.view; +import static org.junit.Assert.assertEquals; + +public class EitherLensTest { + + @Test + public void rightFocusesOnRightValues() { + Lens.Simple, Optional> right = EitherLens.right(); + + assertEquals(Optional.of(1), view(right, right(1))); + assertEquals(Optional.empty(), view(right, left("fail"))); + assertEquals(right(2), set(right, Optional.of(2), right(1))); + assertEquals(right(1), set(right, Optional.empty(), right(1))); + assertEquals(right(2), set(right, Optional.of(2), left("fail"))); + assertEquals(left("fail"), set(right, Optional.empty(), left("fail"))); + } + + @Test + public void leftFocusesOnLeftValues() { + Lens.Simple, Optional> left = EitherLens.left(); + + assertEquals(Optional.of("fail"), view(left, left("fail"))); + assertEquals(Optional.empty(), view(left, right(1))); + assertEquals(left("foo"), set(left, Optional.of("foo"), left("fail"))); + assertEquals(left("fail"), set(left, Optional.empty(), left("fail"))); + assertEquals(left("foo"), set(left, Optional.of("foo"), right(1))); + assertEquals(right(1), set(left, Optional.empty(), right(1))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/OptionalLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/OptionalLensTest.java new file mode 100644 index 000000000..708a57704 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/OptionalLensTest.java @@ -0,0 +1,23 @@ +package com.jnape.palatable.lambda.lens.lenses; + +import com.jnape.palatable.lambda.lens.Lens; +import org.junit.Test; + +import java.util.Optional; + +import static com.jnape.palatable.lambda.lens.functions.Set.set; +import static com.jnape.palatable.lambda.lens.functions.View.view; +import static org.junit.Assert.assertEquals; + +public class OptionalLensTest { + + @Test + public void asOptionalWrapsValuesInOptional() { + Lens.Simple> asOptional = OptionalLens.asOptional(); + + assertEquals(Optional.of("foo"), view(asOptional, "foo")); + assertEquals(Optional.empty(), view(asOptional, null)); + assertEquals("bar", set(asOptional, Optional.of("bar"), "foo")); + assertEquals("foo", set(asOptional, Optional.empty(), "foo")); + } +} \ No newline at end of file From 34cd6a1df8f23f53baf220b341778fa2acdd760e Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 17 Jul 2016 15:32:06 -0500 Subject: [PATCH 16/29] adding referentially transparent map lenses; Lens.Simple now has its own Simple overloads --- .../com/jnape/palatable/lambda/lens/Lens.java | 21 ++++ .../palatable/lambda/lens/lenses/MapLens.java | 38 +++++++ .../lens/lenses/impure/ImpureMapLens.java | 4 +- .../lambda/lens/lenses/MapLensTest.java | 107 ++++++++++++++++++ .../lens/lenses/impure/ImpureMapLensTest.java | 49 +++++--- 5 files changed, 199 insertions(+), 20 deletions(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java create mode 100644 src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java diff --git a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java index 830c00057..4dd5c96e8 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java @@ -49,9 +49,30 @@ static Lens.Simple simpleLens(Fn1 getter, return lens(getter, setter)::apply; } + @FunctionalInterface interface Simple extends Lens { + + @Override + default , FA extends Functor> Fixed fix() { + return Lens.super.fix()::apply; + } + + @SuppressWarnings("unchecked") + default Lens.Simple compose(Lens.Simple g) { + return Lens.super.compose(g)::apply; + } + + default Lens.Simple andThen(Lens.Simple f) { + return f.compose(this); + } + + @FunctionalInterface + interface Fixed, FA extends Functor> + extends Lens.Fixed { + } } + @FunctionalInterface interface Fixed, FB extends Functor> extends Fn2, S, FT> { } diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java new file mode 100644 index 000000000..ae3442830 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java @@ -0,0 +1,38 @@ +package com.jnape.palatable.lambda.lens.lenses; + +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.lens.Lens; +import com.jnape.palatable.lambda.lens.lenses.impure.ImpureMapLens; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import static com.jnape.palatable.lambda.lens.Lens.simpleLens; + +public class MapLens { + + private MapLens() { + } + + public static Lens.Simple, Map> asCopy() { + return simpleLens(HashMap::new, (__, copy) -> copy); + } + + public static Lens.Simple, V> atKey(K k) { + return ImpureMapLens.atKey(k).compose(asCopy()); + } + + public static Lens.Simple, Set> keys() { + return ImpureMapLens.keys().compose(asCopy()); + } + + public static Lens, Map, Collection, Fn2> values() { + return ImpureMapLens.values().compose(asCopy()); + } + + public static Lens.Simple, Map> inverted() { + return ImpureMapLens.inverted().compose(asCopy()); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLens.java index 298b3330f..065e6c334 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLens.java @@ -41,14 +41,14 @@ public static Lens, Map, Collection, Fn2> val }); } - public static Lens.Simple, Map> invert() { + public static Lens.Simple, Map> inverted() { return simpleLens(m -> { Map inverted = new HashMap<>(); m.entrySet().forEach(entry -> inverted.put(entry.getValue(), entry.getKey())); return inverted; }, (m, im) -> { m.clear(); - m.putAll(view(invert(), im)); + m.putAll(view(inverted(), im)); return m; }); } diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java new file mode 100644 index 000000000..69d7e3f5e --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java @@ -0,0 +1,107 @@ +package com.jnape.palatable.lambda.lens.lenses; + +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.lens.Lens; +import org.junit.Before; +import org.junit.Test; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static com.jnape.palatable.lambda.lens.functions.Set.set; +import static com.jnape.palatable.lambda.lens.functions.View.view; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonMap; +import static java.util.Collections.unmodifiableMap; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThat; + +public class MapLensTest { + + private Map m; + + @Before + public void setUp() throws Exception { + m = unmodifiableMap(new HashMap() {{ + put("foo", 1); + put("bar", 2); + put("baz", 3); + }}); + } + + @Test + public void asCopyFocusesOnMapThroughCopy() { + Lens.Simple, Map> asCopy = MapLens.asCopy(); + + assertEquals(m, view(asCopy, m)); + assertNotSame(m, view(asCopy, m)); + + Map update = new HashMap() {{ + put("quux", 0); + }}; + assertEquals(update, set(asCopy, update, m)); + assertSame(update, set(asCopy, update, m)); + } + + @Test + public void atKeyFocusesOnValueAtKey() { + Lens.Simple, Integer> atFoo = MapLens.atKey("foo"); + + assertEquals((Integer) 1, view(atFoo, m)); + assertEquals(new HashMap() {{ + put("foo", -1); + put("bar", 2); + put("baz", 3); + }}, set(atFoo, -1, m)); + } + + @Test + public void keysFocusesOnKeysWithImmutableSet() { + Lens.Simple, Set> keys = MapLens.keys(); + + assertEquals(m.keySet(), view(keys, m)); + view(keys, m).clear(); + assertEquals(new HashSet<>(asList("foo", "bar", "baz")), m.keySet()); + + assertEquals(new HashMap() {{ + put("bar", 2); + put("baz", 3); + put("quux", null); + }}, set(keys, new HashSet<>(asList("bar", "baz", "quux")), m)); + } + + @Test + public void valuesFocusesOnValues() { + Lens, Map, Collection, Fn2> values = MapLens.values(); + + assertThat(view(values, m), + containsInAnyOrder(m.values().toArray())); + + assertEquals(new HashMap() {{ + put("foo", 4); + put("bar", 5); + put("baz", 6); + }}, set(values, (k, v) -> k.length() + v, m)); + } + + @Test + public void invertedFocusesOnMapWithKeysAndValuesSwitched() { + Lens.Simple, Map> inverted = MapLens.inverted(); + + assertEquals(new HashMap() {{ + put(1, "foo"); + put(2, "bar"); + put(3, "baz"); + }}, view(inverted, m)); + + assertEquals(new HashMap() {{ + put("quux", -1); + }}, set(inverted, singletonMap(-1, "quux"), m)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLensTest.java index de0ee33a3..a90c89abc 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLensTest.java @@ -11,12 +11,12 @@ import java.util.Map; import java.util.Set; -import static com.jnape.palatable.lambda.lens.functions.Over.over; import static com.jnape.palatable.lambda.lens.functions.Set.set; import static com.jnape.palatable.lambda.lens.functions.View.view; import static com.jnape.palatable.lambda.lens.lenses.impure.ImpureMapLens.keys; import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; public class ImpureMapLensTest { @@ -36,51 +36,64 @@ public void atKeyFocusesOnValueAtKey() { Lens, Map, Integer, Integer> atFoo = ImpureMapLens.atKey("foo"); assertEquals((Integer) 1, view(atFoo, m)); - assertEquals((Integer) (-1), view(atFoo, set(atFoo, -1, m))); - assertEquals((Integer) (-2), view(atFoo, over(atFoo, x -> x * 2, m))); + + Map updated = set(atFoo, -1, m); + assertEquals(new HashMap() {{ + put("foo", -1); + put("bar", 2); + put("baz", 3); + }}, updated); + assertSame(m, updated); } @Test - public void keysFocusOnKeys() { + public void keysFocusesOnKeys() { Lens, Map, Set, Set> keys = keys(); assertEquals(m.keySet(), view(keys, m)); + + Map updated = set(keys, new HashSet<>(asList("bar", "baz", "quux")), m); assertEquals(new HashMap() {{ put("bar", 2); put("baz", 3); put("quux", null); - }}, set(keys, new HashSet<>(asList("bar", "baz", "quux")), m)); + }}, updated); + assertSame(m, updated); } @Test - public void valuesFocusOnValues() { + public void valuesFocusesOnValues() { Lens, Map, Collection, Fn2> values = ImpureMapLens.values(); assertEquals(m.values(), view(values, m)); + + Map updated = set(values, (k, v) -> k.length() + v, m); assertEquals(new HashMap() {{ - put("foo", 4); - put("bar", 5); - put("baz", 6); - }}, - set(values, (k, v) -> k.length() + v, m)); + put("foo", 4); + put("bar", 5); + put("baz", 6); + }}, updated); + assertSame(m, updated); } @Test - public void invertFlipsKeysAndValues() { - Lens.Simple, Map> invert = ImpureMapLens.invert(); + public void invertedFocusesOnMapWithKeysAndValuesSwitched() { + Lens.Simple, Map> inverted = ImpureMapLens.inverted(); assertEquals(new HashMap() {{ put(1, "foo"); put(2, "bar"); put(3, "baz"); - }}, view(invert, m)); + }}, view(inverted, m)); + Map updated = set(inverted, new HashMap() {{ + put(2, "bar"); + put(3, "baz"); + }}, m); assertEquals(new HashMap() {{ put("bar", 2); put("baz", 3); - }}, set(invert, new HashMap() {{ - put(2, "bar"); - put(3, "baz"); - }}, m)); + }}, updated); + assertSame(m, updated); } } \ No newline at end of file From a693874b93426235bbf782f0faa5f65c4caaf812 Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 24 Jul 2016 13:42:40 -0500 Subject: [PATCH 17/29] Removing pure/impure distinction in Lenses, given any lens composed with asCopy() yields a pure lens. --- ...ollectionLens.java => CollectionLens.java} | 9 +- .../ImpureListLens.java => ListLens.java} | 11 ++- .../palatable/lambda/lens/lenses/MapLens.java | 33 +++++-- .../lens/lenses/impure/ImpureMapLens.java | 55 ----------- ...nLensTest.java => CollectionLensTest.java} | 21 +++- ...ureListLensTest.java => ListLensTest.java} | 19 +++- .../lambda/lens/lenses/MapLensTest.java | 46 +++++---- .../lens/lenses/impure/ImpureMapLensTest.java | 99 ------------------- 8 files changed, 101 insertions(+), 192 deletions(-) rename src/main/java/com/jnape/palatable/lambda/lens/lenses/{impure/ImpureCollectionLens.java => CollectionLens.java} (69%) rename src/main/java/com/jnape/palatable/lambda/lens/lenses/{impure/ImpureListLens.java => ListLens.java} (65%) delete mode 100644 src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLens.java rename src/test/java/com/jnape/palatable/lambda/lens/lenses/{impure/ImpureCollectionLensTest.java => CollectionLensTest.java} (70%) rename src/test/java/com/jnape/palatable/lambda/lens/lenses/{impure/ImpureListLensTest.java => ListLensTest.java} (61%) delete mode 100644 src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLensTest.java diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureCollectionLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/CollectionLens.java similarity index 69% rename from src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureCollectionLens.java rename to src/main/java/com/jnape/palatable/lambda/lens/lenses/CollectionLens.java index dc5b838af..2691e7a1f 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureCollectionLens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/CollectionLens.java @@ -1,5 +1,6 @@ -package com.jnape.palatable.lambda.lens.lenses.impure; +package com.jnape.palatable.lambda.lens.lenses; +import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.lens.Lens; import java.util.Collection; @@ -9,7 +10,11 @@ import static com.jnape.palatable.lambda.lens.Lens.simpleLens; -public class ImpureCollectionLens { +public class CollectionLens { + + public static > Lens.Simple asCopy(Fn1 copyFn) { + return simpleLens(copyFn, (__, copy) -> copy); + } public static > Lens.Simple> asSet() { return simpleLens(HashSet::new, (xsL, xsS) -> { diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureListLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/ListLens.java similarity index 65% rename from src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureListLens.java rename to src/main/java/com/jnape/palatable/lambda/lens/lenses/ListLens.java index 3a3b92793..f03f2cc0b 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureListLens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/ListLens.java @@ -1,14 +1,19 @@ -package com.jnape.palatable.lambda.lens.lenses.impure; +package com.jnape.palatable.lambda.lens.lenses; import com.jnape.palatable.lambda.lens.Lens; +import java.util.ArrayList; import java.util.List; import static com.jnape.palatable.lambda.lens.Lens.simpleLens; -public final class ImpureListLens { +public final class ListLens { - private ImpureListLens() { + private ListLens() { + } + + public static Lens.Simple, List> asCopy() { + return simpleLens(ArrayList::new, (xs, ys) -> ys); } public static Lens.Simple, X> at(int index) { diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java index ae3442830..bfc9d0730 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java @@ -2,16 +2,17 @@ import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.lens.Lens; -import com.jnape.palatable.lambda.lens.lenses.impure.ImpureMapLens; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Set; +import static com.jnape.palatable.lambda.lens.Lens.lens; import static com.jnape.palatable.lambda.lens.Lens.simpleLens; +import static com.jnape.palatable.lambda.lens.functions.View.view; -public class MapLens { +public final class MapLens { private MapLens() { } @@ -21,18 +22,38 @@ public static Lens.Simple, Map> asCopy() { } public static Lens.Simple, V> atKey(K k) { - return ImpureMapLens.atKey(k).compose(asCopy()); + return simpleLens(m -> m.get(k), (m, v) -> { + m.put(k, v); + return m; + }); } public static Lens.Simple, Set> keys() { - return ImpureMapLens.keys().compose(asCopy()); + return simpleLens(Map::keySet, (m, ks) -> { + Set keys = m.keySet(); + keys.retainAll(ks); + ks.removeAll(keys); + ks.forEach(k -> m.put(k, null)); + return m; + }); } public static Lens, Map, Collection, Fn2> values() { - return ImpureMapLens.values().compose(asCopy()); + return lens(Map::values, (m, kvFn) -> { + m.entrySet().forEach(entry -> entry.setValue(kvFn.apply(entry.getKey(), entry.getValue()))); + return m; + }); } public static Lens.Simple, Map> inverted() { - return ImpureMapLens.inverted().compose(asCopy()); + return simpleLens(m -> { + Map inverted = new HashMap<>(); + m.entrySet().forEach(entry -> inverted.put(entry.getValue(), entry.getKey())); + return inverted; + }, (m, im) -> { + m.clear(); + m.putAll(view(inverted(), im)); + return m; + }); } } diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLens.java deleted file mode 100644 index 065e6c334..000000000 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLens.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.jnape.palatable.lambda.lens.lenses.impure; - -import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.lens.Lens; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -import static com.jnape.palatable.lambda.lens.Lens.lens; -import static com.jnape.palatable.lambda.lens.Lens.simpleLens; -import static com.jnape.palatable.lambda.lens.functions.View.view; - -public final class ImpureMapLens { - - private ImpureMapLens() { - } - - public static Lens.Simple, V> atKey(K k) { - return simpleLens(m -> m.get(k), (m, v) -> { - m.put(k, v); - return m; - }); - } - - public static Lens.Simple, Set> keys() { - return simpleLens(Map::keySet, (m, ks) -> { - Set keys = m.keySet(); - keys.retainAll(ks); - ks.removeAll(keys); - ks.forEach(k -> m.put(k, null)); - return m; - }); - } - - public static Lens, Map, Collection, Fn2> values() { - return lens(Map::values, (m, kvFn) -> { - m.entrySet().forEach(entry -> entry.setValue(kvFn.apply(entry.getKey(), entry.getValue()))); - return m; - }); - } - - public static Lens.Simple, Map> inverted() { - return simpleLens(m -> { - Map inverted = new HashMap<>(); - m.entrySet().forEach(entry -> inverted.put(entry.getValue(), entry.getKey())); - return inverted; - }, (m, im) -> { - m.clear(); - m.putAll(view(inverted(), im)); - return m; - }); - } -} diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureCollectionLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/CollectionLensTest.java similarity index 70% rename from src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureCollectionLensTest.java rename to src/test/java/com/jnape/palatable/lambda/lens/lenses/CollectionLensTest.java index 9d8e20097..2df8dd73a 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureCollectionLensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/CollectionLensTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.lens.lenses.impure; +package com.jnape.palatable.lambda.lens.lenses; import com.jnape.palatable.lambda.lens.Lens; import org.junit.Before; @@ -18,8 +18,10 @@ import static java.util.Collections.singleton; import static java.util.stream.Collectors.toList; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; -public class ImpureCollectionLensTest { +public class CollectionLensTest { private List xs; @@ -32,9 +34,20 @@ public void setUp() { }}; } + @Test + public void asCopyUsesMappingFunctionToFocusOnCollectionThroughCopy() { + Lens.Simple, List> asCopy = CollectionLens.asCopy(ArrayList::new); + + assertEquals(xs, view(asCopy, xs)); + assertNotSame(xs, view(asCopy, xs)); + + List updatedList = asList("foo", "bar"); + assertSame(updatedList, set(asCopy, updatedList, xs)); + } + @Test public void asSetFocusesOnCollectionAsSet() { - Lens.Simple, Set> asSet = ImpureCollectionLens.asSet(); + Lens.Simple, Set> asSet = CollectionLens.asSet(); assertEquals(new HashSet<>(xs), view(asSet, xs)); assertEquals(singleton("foo"), view(asSet, asList("foo", "foo"))); @@ -51,7 +64,7 @@ public void asSetFocusesOnCollectionAsSet() { @Test public void asStreamFocusesOnCollectionAsStream() { - Lens.Simple, Stream> asStream = ImpureCollectionLens.asStream(); + Lens.Simple, Stream> asStream = CollectionLens.asStream(); assertEquals(xs, view(asStream, xs).collect(toList())); assertEquals(asList("foo", "bar"), set(asStream, Stream.of("foo", "bar"), xs)); diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureListLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/ListLensTest.java similarity index 61% rename from src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureListLensTest.java rename to src/test/java/com/jnape/palatable/lambda/lens/lenses/ListLensTest.java index 03e7eda39..321d6e802 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureListLensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/ListLensTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.lens.lenses.impure; +package com.jnape.palatable.lambda.lens.lenses; import com.jnape.palatable.lambda.lens.Lens; import org.junit.Before; @@ -12,8 +12,10 @@ import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; -public class ImpureListLensTest { +public class ListLensTest { private List xs; @@ -26,9 +28,20 @@ public void setUp() throws Exception { }}; } + @Test + public void asCopyFocusesOnListThroughCopy() { + Lens.Simple, List> asCopy = ListLens.asCopy(); + + assertEquals(xs, view(asCopy, xs)); + assertNotSame(xs, view(asCopy, xs)); + + List update = asList("foo", "bar", "baz", "quux"); + assertSame(update, set(asCopy, update, xs)); + } + @Test public void atFocusesOnElementAtIndex() { - Lens.Simple, String> at0 = ImpureListLens.at(0); + Lens.Simple, String> at0 = ListLens.at(0); assertEquals("foo", view(at0, xs)); assertEquals(null, view(at0, emptyList())); diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java index 69d7e3f5e..f12bb4dcb 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java @@ -13,26 +13,23 @@ import static com.jnape.palatable.lambda.lens.functions.Set.set; import static com.jnape.palatable.lambda.lens.functions.View.view; +import static com.jnape.palatable.lambda.lens.lenses.MapLens.keys; import static java.util.Arrays.asList; -import static java.util.Collections.singletonMap; -import static java.util.Collections.unmodifiableMap; -import static org.hamcrest.Matchers.containsInAnyOrder; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertThat; public class MapLensTest { private Map m; @Before - public void setUp() throws Exception { - m = unmodifiableMap(new HashMap() {{ + public void setUp() { + m = new HashMap() {{ put("foo", 1); put("bar", 2); put("baz", 3); - }}); + }}; } @Test @@ -45,49 +42,52 @@ public void asCopyFocusesOnMapThroughCopy() { Map update = new HashMap() {{ put("quux", 0); }}; - assertEquals(update, set(asCopy, update, m)); assertSame(update, set(asCopy, update, m)); } @Test public void atKeyFocusesOnValueAtKey() { - Lens.Simple, Integer> atFoo = MapLens.atKey("foo"); + Lens, Map, Integer, Integer> atFoo = MapLens.atKey("foo"); assertEquals((Integer) 1, view(atFoo, m)); + + Map updated = set(atFoo, -1, m); assertEquals(new HashMap() {{ put("foo", -1); put("bar", 2); put("baz", 3); - }}, set(atFoo, -1, m)); + }}, updated); + assertSame(m, updated); } @Test - public void keysFocusesOnKeysWithImmutableSet() { - Lens.Simple, Set> keys = MapLens.keys(); + public void keysFocusesOnKeys() { + Lens, Map, Set, Set> keys = keys(); assertEquals(m.keySet(), view(keys, m)); - view(keys, m).clear(); - assertEquals(new HashSet<>(asList("foo", "bar", "baz")), m.keySet()); + Map updated = set(keys, new HashSet<>(asList("bar", "baz", "quux")), m); assertEquals(new HashMap() {{ put("bar", 2); put("baz", 3); put("quux", null); - }}, set(keys, new HashSet<>(asList("bar", "baz", "quux")), m)); + }}, updated); + assertSame(m, updated); } @Test public void valuesFocusesOnValues() { Lens, Map, Collection, Fn2> values = MapLens.values(); - assertThat(view(values, m), - containsInAnyOrder(m.values().toArray())); + assertEquals(m.values(), view(values, m)); + Map updated = set(values, (k, v) -> k.length() + v, m); assertEquals(new HashMap() {{ put("foo", 4); put("bar", 5); put("baz", 6); - }}, set(values, (k, v) -> k.length() + v, m)); + }}, updated); + assertSame(m, updated); } @Test @@ -100,8 +100,14 @@ public void invertedFocusesOnMapWithKeysAndValuesSwitched() { put(3, "baz"); }}, view(inverted, m)); + Map updated = set(inverted, new HashMap() {{ + put(2, "bar"); + put(3, "baz"); + }}, m); assertEquals(new HashMap() {{ - put("quux", -1); - }}, set(inverted, singletonMap(-1, "quux"), m)); + put("bar", 2); + put("baz", 3); + }}, updated); + assertSame(m, updated); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLensTest.java deleted file mode 100644 index a90c89abc..000000000 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/impure/ImpureMapLensTest.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.jnape.palatable.lambda.lens.lenses.impure; - -import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.lens.Lens; -import org.junit.Before; -import org.junit.Test; - -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import static com.jnape.palatable.lambda.lens.functions.Set.set; -import static com.jnape.palatable.lambda.lens.functions.View.view; -import static com.jnape.palatable.lambda.lens.lenses.impure.ImpureMapLens.keys; -import static java.util.Arrays.asList; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertSame; - -public class ImpureMapLensTest { - - private Map m; - - @Before - public void setUp() { - m = new HashMap() {{ - put("foo", 1); - put("bar", 2); - put("baz", 3); - }}; - } - - @Test - public void atKeyFocusesOnValueAtKey() { - Lens, Map, Integer, Integer> atFoo = ImpureMapLens.atKey("foo"); - - assertEquals((Integer) 1, view(atFoo, m)); - - Map updated = set(atFoo, -1, m); - assertEquals(new HashMap() {{ - put("foo", -1); - put("bar", 2); - put("baz", 3); - }}, updated); - assertSame(m, updated); - } - - @Test - public void keysFocusesOnKeys() { - Lens, Map, Set, Set> keys = keys(); - - assertEquals(m.keySet(), view(keys, m)); - - Map updated = set(keys, new HashSet<>(asList("bar", "baz", "quux")), m); - assertEquals(new HashMap() {{ - put("bar", 2); - put("baz", 3); - put("quux", null); - }}, updated); - assertSame(m, updated); - } - - @Test - public void valuesFocusesOnValues() { - Lens, Map, Collection, Fn2> values = ImpureMapLens.values(); - - assertEquals(m.values(), view(values, m)); - - Map updated = set(values, (k, v) -> k.length() + v, m); - assertEquals(new HashMap() {{ - put("foo", 4); - put("bar", 5); - put("baz", 6); - }}, updated); - assertSame(m, updated); - } - - @Test - public void invertedFocusesOnMapWithKeysAndValuesSwitched() { - Lens.Simple, Map> inverted = ImpureMapLens.inverted(); - - assertEquals(new HashMap() {{ - put(1, "foo"); - put(2, "bar"); - put(3, "baz"); - }}, view(inverted, m)); - - Map updated = set(inverted, new HashMap() {{ - put(2, "bar"); - put(3, "baz"); - }}, m); - assertEquals(new HashMap() {{ - put("bar", 2); - put("baz", 3); - }}, updated); - assertSame(m, updated); - } -} \ No newline at end of file From 8750e043870bd57f1b2a217c74d97f0be45ce435 Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 24 Jul 2016 14:54:31 -0500 Subject: [PATCH 18/29] Removing redundant functor tests from Fn2/Fn3, adding missing profunctor test to Fn1; Lens is now a Profunctor --- .../com/jnape/palatable/lambda/lens/Lens.java | 20 ++++++++++++++++++- .../palatable/lambda/functions/Fn1Test.java | 18 +++++++++++++---- .../palatable/lambda/functions/Fn2Test.java | 19 ------------------ .../palatable/lambda/functions/Fn3Test.java | 16 --------------- .../jnape/palatable/lambda/lens/LensTest.java | 18 ++++++++--------- 5 files changed, 41 insertions(+), 50 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java index 4dd5c96e8..fa101863f 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java @@ -3,6 +3,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.Profunctor; import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; import static com.jnape.palatable.lambda.lens.functions.Over.over; @@ -10,7 +11,7 @@ import static com.jnape.palatable.lambda.lens.functions.View.view; @FunctionalInterface -public interface Lens extends Functor { +public interface Lens extends Functor, Profunctor { , FB extends Functor> FT apply(Fn1 fn, S s); @@ -23,6 +24,23 @@ default Lens fmap(Fn1 fn) { return this.compose(Lens.lens(id(), (s, t) -> fn.apply(t))); } + @Override + @SuppressWarnings("unchecked") + default Lens diMapL(Fn1 fn) { + return (Lens) Profunctor.super.diMapL(fn); + } + + @Override + @SuppressWarnings("unchecked") + default Lens diMapR(Fn1 fn) { + return (Lens) Profunctor.super.diMapR(fn); + } + + @Override + default Lens diMap(Fn1 lFn, Fn1 rFn) { + return this.compose(lens(lFn, (r, t) -> rFn.apply(t))); + } + default Lens andThen(Lens f) { return f.compose(this); } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java b/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java index 06ef3d6cf..2bdfca588 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java @@ -1,18 +1,28 @@ package com.jnape.palatable.lambda.functions; +import org.hamcrest.MatcherAssert; import org.junit.Test; -import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertEquals; public class Fn1Test { @Test - public void fmapComposesFunctions() { + public void functorProperties() { Fn1 add2 = integer -> integer + 2; Fn1 toString = Object::toString; - assertThat(add2.fmap(toString).apply(2), is(toString.apply(add2.apply(2)))); + MatcherAssert.assertThat(add2.fmap(toString).apply(2), is(toString.apply(add2.apply(2)))); + } + + @Test + public void profunctorProperties() { + Fn1 add2 = integer -> integer + 2; + + assertEquals((Integer) 3, add2.diMapL(Integer::parseInt).apply("1")); + assertEquals("3", add2.diMapR(Object::toString).apply(1)); + assertEquals("3", add2.diMap(Integer::parseInt, Object::toString).apply("1")); } @Test @@ -20,6 +30,6 @@ public void thenIsJustAnAliasForFmap() { Fn1 add2 = integer -> integer + 2; Fn1 toString = Object::toString; - assertThat(add2.then(toString).apply(2), is(toString.apply(add2.apply(2)))); + MatcherAssert.assertThat(add2.then(toString).apply(2), is(toString.apply(add2.apply(2)))); } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/Fn2Test.java b/src/test/java/com/jnape/palatable/lambda/functions/Fn2Test.java index 49d7e9809..e12b9e3e3 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/Fn2Test.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/Fn2Test.java @@ -1,6 +1,5 @@ package com.jnape.palatable.lambda.functions; -import com.jnape.palatable.lambda.functions.builtin.fn1.Id; import org.junit.Test; import java.util.function.BiFunction; @@ -30,24 +29,6 @@ public void uncurries() { assertThat(CHECK_LENGTH.uncurry().apply(tuple("abc", 3)), is(true)); } - @Test - public void functorProperties() { - assertThat(CHECK_LENGTH.fmap(f -> Id.id()).apply("foo").apply("bar"), is("bar")); - } - - @Test - public void profunctorProperties() { - assertThat(CHECK_LENGTH.diMapL(Object::toString).apply(123).apply(3), is(true)); - assertThat(CHECK_LENGTH.diMapR(fn -> fn.andThen(Object::toString)).apply("123").apply(3), is("true")); - assertThat( - CHECK_LENGTH.>diMap( - Object::toString, - fn -> fn.andThen(Object::toString) - ).apply("123").apply(3), - is("true") - ); - } - @Test public void toBiFunction() { BiFunction biFunction = CHECK_LENGTH.toBiFunction(); diff --git a/src/test/java/com/jnape/palatable/lambda/functions/Fn3Test.java b/src/test/java/com/jnape/palatable/lambda/functions/Fn3Test.java index c35532933..7214064f2 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/Fn3Test.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/Fn3Test.java @@ -28,20 +28,4 @@ public void flipsFirstAndSecondArgument() { public void uncurries() { assertThat(CHECK_MULTIPLICATION.uncurry().apply(tuple(2, 3), 6), is(true)); } - - @Test - public void functorProperties() { - assertThat(CHECK_MULTIPLICATION.fmap(f -> f.fmap(g -> g.andThen(Object::toString))).apply(2).apply(3).apply(6), is("true")); - } - - @Test - public void profunctorProperties() { - assertThat(CHECK_MULTIPLICATION.diMapL(Integer::parseInt).apply("2").apply(3).apply(6), is(true)); - assertThat(CHECK_MULTIPLICATION.diMapR(f -> f.fmap(g -> g.andThen(Object::toString))).apply(2).apply(3).apply(6), is("true")); - assertThat(CHECK_MULTIPLICATION.diMap((Fn1) Integer::parseInt, - f -> f.fmap(g -> g.andThen(Object::toString))) - .apply("2") - .apply(3) - .apply(6), is("true")); - } } diff --git a/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java index 84609ba26..9b45928c6 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java @@ -5,6 +5,7 @@ import com.jnape.palatable.lambda.functor.builtin.Identity; import org.junit.Test; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; @@ -25,21 +26,13 @@ public class LensTest { @Test public void setsUnderIdentity() { - Set ints = LENS.>, Identity>apply( - s -> new Identity<>(s.length()), - asList("foo", "bar", "baz") - ).runIdentity(); - + Set ints = LENS.>, Identity>apply(s -> new Identity<>(s.length()), asList("foo", "bar", "baz")).runIdentity(); assertEquals(singleton(3), ints); } @Test public void viewsUnderConst() { - Integer i = LENS.>, Const>apply( - s -> new Const<>(s.length()), - asList("foo", "bar", "baz") - ).runConst(); - + Integer i = LENS.>, Const>apply(s -> new Const<>(s.length()), asList("foo", "bar", "baz")).runConst(); assertEquals((Integer) 3, i); } @@ -59,6 +52,11 @@ public void functorProperties() { assertEquals(false, set(LENS.fmap(Set::isEmpty), 1, singletonList("foo"))); } + @Test + public void profunctorProperties() { + assertEquals(false, set(LENS.diMap(ArrayList::new, Set::isEmpty), 2, singleton("foo"))); + } + @Test public void composition() { Map> map = singletonMap("foo", asList("one", "two", "three")); From 970b4c1272b455ecd2b78dd693c12a21493e8869 Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 7 Aug 2016 15:08:30 -0500 Subject: [PATCH 19/29] Getting lenses in line with master changes. --- .../lambda/functor/builtin/Const.java | 13 +++++---- .../lambda/functor/builtin/Identity.java | 15 ++-------- .../com/jnape/palatable/lambda/lens/Lens.java | 28 ++++++++++--------- .../palatable/lambda/lens/functions/Over.java | 14 ++++++---- .../lambda/lens/lenses/CollectionLens.java | 4 +-- 5 files changed, 35 insertions(+), 39 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java index e04739e46..3dd9e03fb 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java @@ -1,9 +1,10 @@ package com.jnape.palatable.lambda.functor.builtin; -import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functor.Bifunctor; import com.jnape.palatable.lambda.functor.Functor; +import java.util.function.Function; + public final class Const implements Functor, Bifunctor { private final A a; @@ -18,25 +19,25 @@ public A runConst() { @Override @SuppressWarnings("unchecked") - public Const fmap(Fn1 fn) { + public Const fmap(Function fn) { return (Const) this; } @Override @SuppressWarnings("unchecked") - public Const biMapL(Fn1 fn) { + public Const biMapL(Function fn) { return (Const) Bifunctor.super.biMapL(fn); } @Override @SuppressWarnings("unchecked") - public Const biMapR(Fn1 fn) { + public Const biMapR(Function fn) { return (Const) Bifunctor.super.biMapR(fn); } @Override - public Const biMap(Fn1 lFn, - Fn1 rFn) { + public Const biMap(Function lFn, + Function rFn) { return new Const<>(lFn.apply(a)); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java index c0e2db683..988ca0c9f 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java @@ -1,8 +1,9 @@ package com.jnape.palatable.lambda.functor.builtin; -import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functor.Functor; +import java.util.function.Function; + public class Identity implements Functor { private final A a; @@ -16,17 +17,7 @@ public A runIdentity() { } @Override - public Identity fmap(Fn1 fn) { + public Identity fmap(Function fn) { return new Identity<>(fn.apply(a)); } - - @Override - public boolean equals(Object other) { - return other instanceof Identity && a.equals(((Identity) other).a); - } - - @Override - public int hashCode() { - return a.hashCode(); - } } diff --git a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java index fa101863f..ae5760f2c 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java @@ -1,10 +1,12 @@ package com.jnape.palatable.lambda.lens; -import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.functor.Functor; import com.jnape.palatable.lambda.functor.Profunctor; +import java.util.function.BiFunction; +import java.util.function.Function; + import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; import static com.jnape.palatable.lambda.lens.functions.Over.over; import static com.jnape.palatable.lambda.lens.functions.Set.set; @@ -13,31 +15,31 @@ @FunctionalInterface public interface Lens extends Functor, Profunctor { - , FB extends Functor> FT apply(Fn1 fn, S s); + , FB extends Functor> FT apply(Function fn, S s); default , FB extends Functor> Fixed fix() { return this::apply; } @Override - default Lens fmap(Fn1 fn) { + default Lens fmap(Function fn) { return this.compose(Lens.lens(id(), (s, t) -> fn.apply(t))); } @Override @SuppressWarnings("unchecked") - default Lens diMapL(Fn1 fn) { + default Lens diMapL(Function fn) { return (Lens) Profunctor.super.diMapL(fn); } @Override @SuppressWarnings("unchecked") - default Lens diMapR(Fn1 fn) { + default Lens diMapR(Function fn) { return (Lens) Profunctor.super.diMapR(fn); } @Override - default Lens diMap(Fn1 lFn, Fn1 rFn) { + default Lens diMap(Function lFn, Function rFn) { return this.compose(lens(lFn, (r, t) -> rFn.apply(t))); } @@ -49,21 +51,21 @@ default Lens compose(Lens g) { return lens(view(g).fmap(view(this)), (q, b) -> over(g, set(this, b), q)); } - static Lens lens(Fn1 getter, - Fn2 setter) { + static Lens lens(Function getter, + BiFunction setter) { return new Lens() { @Override @SuppressWarnings("unchecked") - public , FB extends Functor> FT apply(Fn1 fn, + public , FB extends Functor> FT apply(Function fn, S s) { - return (FT) fn.apply(getter.apply(s)).fmap(setter.apply(s)); + return (FT) fn.apply(getter.apply(s)).fmap(b -> setter.apply(s, b)); } }; } @SuppressWarnings("unchecked") - static Lens.Simple simpleLens(Fn1 getter, - Fn2 setter) { + static Lens.Simple simpleLens(Function getter, + BiFunction setter) { return lens(getter, setter)::apply; } @@ -92,6 +94,6 @@ interface Fixed, FA extends Functor> @FunctionalInterface interface Fixed, FB extends Functor> - extends Fn2, S, FT> { + extends Fn2, S, FT> { } } diff --git a/src/main/java/com/jnape/palatable/lambda/lens/functions/Over.java b/src/main/java/com/jnape/palatable/lambda/lens/functions/Over.java index 4ee24be65..49ff92930 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/functions/Over.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/functions/Over.java @@ -6,15 +6,17 @@ import com.jnape.palatable.lambda.functor.builtin.Identity; import com.jnape.palatable.lambda.lens.Lens; -public final class Over implements Fn3, Fn1, S, T> { +import java.util.function.Function; + +public final class Over implements Fn3, Function, S, T> { private Over() { } @Override - public T apply(Lens lens, Fn1 fn, S s) { + public T apply(Lens lens, Function fn, S s) { return lens., Identity>fix() - .apply(fn.fmap((Fn1>) Identity::new), s) + .apply(fn.andThen((Function>) Identity::new), s) .runIdentity(); } @@ -22,17 +24,17 @@ public static Over over() { return new Over<>(); } - public static Fn2, S, T> over( + public static Fn2, S, T> over( Lens lens) { return Over.over().apply(lens); } public static Fn1 over(Lens lens, - Fn1 fn) { + Function fn) { return over(lens).apply(fn); } - public static T over(Lens lens, Fn1 fn, S s) { + public static T over(Lens lens, Function fn, S s) { return over(lens, fn).apply(s); } } diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/CollectionLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/CollectionLens.java index 2691e7a1f..edbbd2297 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/CollectionLens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/CollectionLens.java @@ -1,18 +1,18 @@ package com.jnape.palatable.lambda.lens.lenses; -import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.lens.Lens; import java.util.Collection; import java.util.HashSet; import java.util.Set; +import java.util.function.Function; import java.util.stream.Stream; import static com.jnape.palatable.lambda.lens.Lens.simpleLens; public class CollectionLens { - public static > Lens.Simple asCopy(Fn1 copyFn) { + public static > Lens.Simple asCopy(Function copyFn) { return simpleLens(copyFn, (__, copy) -> copy); } From 2d746f9aaf553fb7c0e6d709e376daf4a70d8829 Mon Sep 17 00:00:00 2001 From: John Napier Date: Sun, 7 Aug 2016 15:10:26 -0500 Subject: [PATCH 20/29] Fixing indentation in TOC --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 52c88d502..aa144b856 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,11 @@ Functional patterns for Java 8 - [Background](#background) - [Installation](#installation) - [Examples](#examples) - - [ADTs](#adts) - - [HLists](#hlists) - - [Tuples](#tuples) - - [HMaps](#hmaps) - - [Either](#either) + - [ADTs](#adts) + - [HLists](#hlists) + - [Tuples](#tuples) + - [HMaps](#hmaps) + - [Either](#either) - [Notes](#notes) - [License](#license) From 3d203b8af78802d936cfb739eafffb6a12190944 Mon Sep 17 00:00:00 2001 From: jnape Date: Mon, 8 Aug 2016 00:59:02 -0500 Subject: [PATCH 21/29] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b192e8e58..c705434d0 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ lambda - 1.4 + 1.5-SNAPSHOT jar Lambda From 79a806654788d83d62094e27b601d63ecc2347bc Mon Sep 17 00:00:00 2001 From: John Napier Date: Fri, 12 Aug 2016 17:28:39 -0500 Subject: [PATCH 22/29] Updating versions in README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index aa144b856..e43c748c8 100644 --- a/README.md +++ b/README.md @@ -46,14 +46,14 @@ Add the following dependency to your: com.jnape.palatable lambda - 1.2 + 1.3 ``` `build.gradle` ([Gradle](https://docs.gradle.org/current/userguide/dependency_management.html)): ```gradle - compile group: 'com.jnape.palatable', name: 'lambda', version: '1.2' + compile group: 'com.jnape.palatable', name: 'lambda', version: '1.3' ``` From d4ef0bdd69c884951580aba2a40f5de5867de434 Mon Sep 17 00:00:00 2001 From: John Napier Date: Tue, 16 Aug 2016 14:14:33 -0500 Subject: [PATCH 23/29] Fixing source example in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e43c748c8..57c889944 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ Let's compose two functions: Fn1 noOp = add.then(subtract); // same as - Fn1 alsoNoOp = subtract.fmap(add); + Fn1 alsoNoOp = subtract.compose(subtract); ``` And partially apply some: From 1522499bba15cdbd035006214841ac4164c2c6ca Mon Sep 17 00:00:00 2001 From: jnape Date: Mon, 15 Aug 2016 10:41:55 -0500 Subject: [PATCH 24/29] Adding HMap#remove and #removeAll --- .../jnape/palatable/lambda/adt/hmap/HMap.java | 24 +++++++- .../palatable/lambda/adt/hmap/HMapTest.java | 57 ++++++++++++++----- 2 files changed, 64 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hmap/HMap.java b/src/main/java/com/jnape/palatable/lambda/adt/hmap/HMap.java index ba15852e3..f9073da12 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hmap/HMap.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hmap/HMap.java @@ -27,7 +27,7 @@ public class HMap implements Iterable> { private final Map table; - HMap(Map table) { + private HMap(Map table) { this.table = table; } @@ -70,7 +70,7 @@ public HMap put(TypeSafeKey key, V value) { /** * Store all the key/value mappings in hMap in this HMap. * - * @param hMap the other hMap + * @param hMap the other HMap * @return the updated HMap */ public HMap putAll(HMap hMap) { @@ -87,6 +87,26 @@ public boolean containsKey(TypeSafeKey key) { return table.containsKey(key); } + /** + * Remove a mapping from this HMap. + * + * @param key the key + * @return the updated HMap + */ + public HMap remove(TypeSafeKey key) { + return alter(t -> t.remove(key)); + } + + /** + * Remove all the key/value mappings in hMap from this HMap. + * + * @param hMap the other HMap + * @return the updated HMap + */ + public HMap removeAll(HMap hMap) { + return alter(t -> t.keySet().removeAll(hMap.table.keySet())); + } + /** * Retrieve all the mapped keys. * diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hmap/HMapTest.java b/src/test/java/com/jnape/palatable/lambda/adt/hmap/HMapTest.java index c89861eea..2edcb1d17 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hmap/HMapTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hmap/HMapTest.java @@ -10,8 +10,6 @@ import static com.jnape.palatable.lambda.adt.hmap.HMap.hMap; import static com.jnape.palatable.lambda.adt.hmap.HMap.singletonHMap; import static com.jnape.palatable.lambda.adt.hmap.TypeSafeKey.typeSafeKey; -import static java.util.Collections.emptyMap; -import static java.util.Collections.singletonMap; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; @@ -26,29 +24,30 @@ public class HMapTest { public void getForPresentKey() { TypeSafeKey stringKey = typeSafeKey(); assertEquals(Optional.of("string value"), - new HMap(singletonMap(stringKey, "string value")).get(stringKey)); + singletonHMap(stringKey, "string value").get(stringKey)); } @Test public void getForAbsentKey() { assertEquals(Optional.empty(), - new HMap(singletonMap(typeSafeKey(), "string value")).get(typeSafeKey())); + singletonHMap(typeSafeKey(), "string value") + .get(typeSafeKey())); } @Test public void getForPresentKeyWithNullValue() { TypeSafeKey stringKey = typeSafeKey(); assertEquals(Optional.empty(), - new HMap(singletonMap(stringKey, null)).get(stringKey)); + singletonHMap(stringKey, null).get(stringKey)); } @Test public void put() { TypeSafeKey stringKey = typeSafeKey(); - assertEquals(new HMap(singletonMap(stringKey, "string value")), + assertEquals(singletonHMap(stringKey, "string value"), emptyHMap().put(stringKey, "string value")); - assertEquals(new HMap(singletonMap(stringKey, "new value")), + assertEquals(singletonHMap(stringKey, "new value"), emptyHMap() .put(stringKey, "string value") .put(stringKey, "new value")); @@ -75,6 +74,35 @@ public void putAll() { right.putAll(left)); } + @Test + public void remove() { + TypeSafeKey stringKey1 = typeSafeKey(); + TypeSafeKey stringKey2 = typeSafeKey(); + assertEquals(emptyHMap(), + emptyHMap() + .put(stringKey1, "string value") + .remove(stringKey1)); + + assertEquals(singletonHMap(stringKey2, "another string value"), + emptyHMap() + .put(stringKey1, "string value") + .put(stringKey2, "another string value") + .remove(stringKey1)); + } + + @Test + public void removeAll() { + TypeSafeKey stringKey1 = typeSafeKey(); + TypeSafeKey stringKey2 = typeSafeKey(); + + HMap hMap1 = hMap(stringKey1, "foo", + stringKey2, "bar"); + HMap hMap2 = singletonHMap(stringKey1, "foo"); + + assertEquals(singletonHMap(stringKey2, "bar"), + hMap1.removeAll(hMap2)); + } + @Test public void containsKey() { TypeSafeKey stringKey1 = typeSafeKey(); @@ -127,8 +155,8 @@ public void convenienceStaticFactoryMethods() { TypeSafeKey stringKey = typeSafeKey(); TypeSafeKey intKey = typeSafeKey(); TypeSafeKey floatKey = typeSafeKey(); - assertEquals(new HMap(emptyMap()), HMap.emptyHMap()); - assertEquals(new HMap(singletonMap(stringKey, "string value")), HMap.singletonHMap(stringKey, "string value")); + assertEquals(emptyHMap().put(stringKey, "string value"), + singletonHMap(stringKey, "string value")); assertEquals(emptyHMap().put(stringKey, "string value").put(intKey, 1), hMap(stringKey, "string value", intKey, 1)); @@ -157,13 +185,12 @@ public void equality() { public void hashCodeUsesDecentDistribution() { assertEquals(emptyHMap().hashCode(), emptyHMap().hashCode()); TypeSafeKey stringKey = typeSafeKey(); - assertEquals(new HMap(singletonMap(stringKey, "string value")).hashCode(), - new HMap(singletonMap(stringKey, "string value")).hashCode()); - - assertNotEquals(emptyHMap(), new HMap(singletonMap(stringKey, "string value"))); - assertNotEquals(new HMap(singletonMap(stringKey, "string value")), - new HMap(singletonMap(stringKey, "another string value"))); + assertEquals(singletonHMap(stringKey, "string value").hashCode(), + singletonHMap(stringKey, "string value").hashCode()); + assertNotEquals(emptyHMap(), singletonHMap(stringKey, "string value")); + assertNotEquals(singletonHMap(stringKey, "string value"), + singletonHMap(stringKey, "another string value")); } @Test From a0e1403a2d4bdeb0ab389b7c6651954c1ce1785b Mon Sep 17 00:00:00 2001 From: jnape Date: Mon, 15 Aug 2016 23:31:04 -0500 Subject: [PATCH 25/29] Either#merge now accepts multiple eithers for merging --- .../com/jnape/palatable/lambda/adt/Either.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/adt/Either.java b/src/main/java/com/jnape/palatable/lambda/adt/Either.java index 4fdd106fd..932b1ff95 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/Either.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/Either.java @@ -12,6 +12,8 @@ import java.util.function.Supplier; import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft.foldLeft; +import static java.util.Arrays.asList; /** * The binary tagged union. General semantics tend to connote "success" values via the right value and "failure" values @@ -120,22 +122,24 @@ public final Either flatMap(FunctionEither<L, R>s into a single + * Given two binary operators over L and R, merge multiple Either<L, R>s into a single * Either<L, R>. Note that merge biases towards left values; that is, if any left * value exists, the result will be a left value, such that only unanimous right values result in an ultimate right * value. * * @param leftFn the binary operator for L * @param rightFn the binary operator for R - * @param other the other Either + * @param others the other Eithers to merge into this one * @return the merged Either */ + @SafeVarargs public final Either merge(BiFunction leftFn, BiFunction rightFn, - Either other) { - return this.match( - l1 -> other.match(l2 -> left(leftFn.apply(l1, l2)), r -> left(l1)), - r1 -> other.match(Either::left, r2 -> right(rightFn.apply(r1, r2)))); + Either... others) { + return foldLeft((x, y) -> x.match(l1 -> y.>match(l2 -> left(leftFn.apply(l1, l2)), r -> left(l1)), + r1 -> y.>match(Either::left, r2 -> right(rightFn.apply(r1, r2)))), + this, + asList(others)); } /** From 80a0fdbe08bc5997205a8ee636ec5bc1e0e4344f Mon Sep 17 00:00:00 2001 From: jnape Date: Tue, 16 Aug 2016 00:38:27 -0500 Subject: [PATCH 26/29] Documenting HList subtypes --- .../palatable/lambda/adt/hlist/HList.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/HList.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/HList.java index aaebee9c7..3c3e792d4 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/HList.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/HList.java @@ -134,6 +134,12 @@ public static <_1, _2, _3, _4, _5> Tuple5<_1, _2, _3, _4, _5> tuple(_1 _1, _2 _2 return tuple(_2, _3, _4, _5).cons(_1); } + /** + * The consing of a head element to a tail HList. + * + * @param the head element type + * @param the HList tail type + */ public static class HCons> extends HList { private final Head head; private final Tail tail; @@ -143,10 +149,20 @@ public static class HCons> extends HListHList. + * + * @return the head element + */ public Head head() { return head; } + /** + * The remaining tail of the HList; returns an HNil if this is the last element. + * + * @return the tail + */ public Tail tail() { return tail; } @@ -180,6 +196,9 @@ public final String toString() { } } + /** + * The empty HList. + */ public static final class HNil extends HList { private static final HNil INSTANCE = new HNil(); From 03dc83173ddbf2b06c92ac42bbe53f37479e5473 Mon Sep 17 00:00:00 2001 From: jnape Date: Fri, 19 Aug 2016 17:11:21 -0400 Subject: [PATCH 27/29] Deciding not to make un-fixed lens a Profunctor --- .../com/jnape/palatable/lambda/lens/Lens.java | 24 +++---------------- .../jnape/palatable/lambda/lens/LensTest.java | 6 ----- 2 files changed, 3 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java index ae5760f2c..b2d13e2e1 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java @@ -2,7 +2,6 @@ import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.functor.Functor; -import com.jnape.palatable.lambda.functor.Profunctor; import java.util.function.BiFunction; import java.util.function.Function; @@ -13,12 +12,12 @@ import static com.jnape.palatable.lambda.lens.functions.View.view; @FunctionalInterface -public interface Lens extends Functor, Profunctor { +public interface Lens extends Functor { , FB extends Functor> FT apply(Function fn, S s); default , FB extends Functor> Fixed fix() { - return this::apply; + return this::apply; } @Override @@ -26,23 +25,6 @@ default Lens fmap(Function fn) { return this.compose(Lens.lens(id(), (s, t) -> fn.apply(t))); } - @Override - @SuppressWarnings("unchecked") - default Lens diMapL(Function fn) { - return (Lens) Profunctor.super.diMapL(fn); - } - - @Override - @SuppressWarnings("unchecked") - default Lens diMapR(Function fn) { - return (Lens) Profunctor.super.diMapR(fn); - } - - @Override - default Lens diMap(Function lFn, Function rFn) { - return this.compose(lens(lFn, (r, t) -> rFn.apply(t))); - } - default Lens andThen(Lens f) { return f.compose(this); } @@ -74,7 +56,7 @@ interface Simple extends Lens { @Override default , FA extends Functor> Fixed fix() { - return Lens.super.fix()::apply; + return this::apply; } @SuppressWarnings("unchecked") diff --git a/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java index 9b45928c6..c9deff678 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java @@ -5,7 +5,6 @@ import com.jnape.palatable.lambda.functor.builtin.Identity; import org.junit.Test; -import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; @@ -52,11 +51,6 @@ public void functorProperties() { assertEquals(false, set(LENS.fmap(Set::isEmpty), 1, singletonList("foo"))); } - @Test - public void profunctorProperties() { - assertEquals(false, set(LENS.diMap(ArrayList::new, Set::isEmpty), 2, singleton("foo"))); - } - @Test public void composition() { Map> map = singletonMap("foo", asList("one", "two", "three")); From e16cb0e960af2cf09d043d28cbf87b9a386cb18d Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 28 Aug 2016 18:51:08 -0500 Subject: [PATCH 28/29] Adding javadocs for lens and friends; opting against native Java behavior and for safer behavior around accessing potentially null values in list and map lenses. --- .../lambda/functor/builtin/Const.java | 45 ++++ .../lambda/functor/builtin/Identity.java | 19 +- .../com/jnape/palatable/lambda/lens/Lens.java | 192 ++++++++++++++++++ .../palatable/lambda/lens/functions/Over.java | 15 ++ .../palatable/lambda/lens/functions/Set.java | 21 +- .../palatable/lambda/lens/functions/View.java | 13 ++ .../lambda/lens/lenses/CollectionLens.java | 32 ++- .../lambda/lens/lenses/EitherLens.java | 21 ++ .../lambda/lens/lenses/ListLens.java | 34 +++- .../palatable/lambda/lens/lenses/MapLens.java | 62 +++++- .../lambda/lens/lenses/OptionalLens.java | 9 + .../lambda/lens/lenses/ListLensTest.java | 7 +- .../lambda/lens/lenses/MapLensTest.java | 23 ++- 13 files changed, 463 insertions(+), 30 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java index 3dd9e03fb..e19261415 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java @@ -5,6 +5,15 @@ import java.util.function.Function; +/** + * A (surprisingly useful) functor over some phantom type B, retaining a value of type A that + * can be retrieved later. This is useful in situations where it is desirable to retain constant information throughout + * arbitrary functor transformations, such that at the end of the chain, regardless of how B has been + * altered, A is still pristine and retrievable. + * + * @param the left parameter type, and the type of the stored value + * @param the right (phantom) parameter type + */ public final class Const implements Functor, Bifunctor { private final A a; @@ -13,28 +22,64 @@ public Const(A a) { this.a = a; } + /** + * Retrieve the stored value. + * + * @return the value + */ public A runConst() { return a; } + /** + * Map over the right parameter. Note that because B is never actually known quantity outside of a type + * signature, this is effectively a no-op that serves only to alter Const's type signature. + * + * @param fn the mapping function + * @param the new right parameter type + * @return a Const over A (the same value) and C (the new phantom parameter) + */ @Override @SuppressWarnings("unchecked") public Const fmap(Function fn) { return (Const) this; } + /** + * Covariantly map over the left parameter type (the value). + * + * @param fn the mapping function + * @param the new left parameter type (the value) + * @return a Const over Z (the new value) and B (the same phantom parameter) + */ @Override @SuppressWarnings("unchecked") public Const biMapL(Function fn) { return (Const) Bifunctor.super.biMapL(fn); } + /** + * Covariantly map over the right parameter (phantom) type. + * + * @param fn the mapping function + * @param the new right parameter (phantom) type + * @return a Const over A (the same value) and C (the new phantom parameter) + */ @Override @SuppressWarnings("unchecked") public Const biMapR(Function fn) { return (Const) Bifunctor.super.biMapR(fn); } + /** + * Bifunctor's biMap, specialized for Const. + * + * @param lFn the left parameter mapping function + * @param rFn the right parameter mapping function + * @param the new left parameter type + * @param the new right parameter type + * @return a Const over C (the new value) and D (the new phantom parameter) + */ @Override public Const biMap(Function lFn, Function rFn) { diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java index 988ca0c9f..04160b05f 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java @@ -4,7 +4,12 @@ import java.util.function.Function; -public class Identity implements Functor { +/** + * A functor over some value of type A that can be mapped over and retrieved later. + * + * @param the value type + */ +public final class Identity implements Functor { private final A a; @@ -12,10 +17,22 @@ public Identity(A a) { this.a = a; } + /** + * Retrieve the value. + * + * @return the value + */ public A runIdentity() { return a; } + /** + * Covariantly map over the value. + * + * @param fn the mapping function + * @param the new value type + * @return an Identity over B (the new value) + */ @Override public Identity fmap(Function fn) { return new Identity<>(fn.apply(a)); diff --git a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java index b2d13e2e1..6717baefc 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java @@ -11,11 +11,141 @@ import static com.jnape.palatable.lambda.lens.functions.Set.set; import static com.jnape.palatable.lambda.lens.functions.View.view; +/** + * An approximation of van Laarhoven lenses. + *

+ * A "lens" can be considered in its simplest form as the conjugation of a "getter" and a "setter"; that is, a + * unification type representing the way to retrieve a "smaller" value A from a "larger" value + * S, as well as a way to update a "smaller" value B of a "larger" value S, + * producing another "larger" value T. + *

+ * Consider the following example: + *

+ * {@code
+ * public final class Person {
+ *     private final int age;
+ *
+ *     public Person(int age) {
+ *         this.age = age;
+ *     }
+ *
+ *     public int getAge() {
+ *         return age;
+ *     }
+ *
+ *     public Person setAge(int age) {
+ *         return new Person(age);
+ *     }
+ * }
+ * }
+ * 
+ * A lens that focused on the age field of an instance of Person might look like this: + *
+ * {@code
+ * Lens ageLens = Lens.lens(Person::getAge, Person::setAge);
+ *
+ * Person adult = new Person(18);
+ * Integer age = view(ageLens, adult); // 18
+ *
+ * Person olderAdult = set(ageLens, 19, adult);
+ * Integer olderAge = view(ageLens, olderAdult); // 19
+ * }
+ * 
+ * The pattern of a getter and setter that mutually agree on both A and B as well as on both + * S and T is so common that this can be given a simplified type signature: + *
+ * {@code
+ * Lens.Simple ageLens = Lens.simpleLens(Person::getAge, Person::setAge);
+ *
+ * Person adult = new Person(18);
+ * Integer age = view(ageLens, adult); // 18
+ *
+ * Person olderAdult = set(ageLens, 19, adult);
+ * Integer olderAge = view(ageLens, olderAdult); // 19
+ * }
+ * 
+ * However, consider if age could be updated on a Person by being provided a date of birth, in + * the form of a LocalDate: + *
+ * {@code
+ * public final class Person {
+ *     private final int age;
+ *
+ *     public Person(int age) {
+ *         this.age = age;
+ *     }
+ *
+ *     public int getAge() {
+ *         return age;
+ *     }
+ *
+ *     public Person setAge(int age) {
+ *         return new Person(age);
+ *     }
+ *
+ *     public Person setAge(LocalDate dob) {
+ *         return setAge((int) YEARS.between(dob, LocalDate.now()));
+ *     }
+ * }
+ * }
+ * 
+ * This is why Lens has both an A and a B: A is the value for "getting", and + * B is the potentially different value for "setting". This distinction makes lenses powerful enough to + * express the more complicated setAge case naturally: + *
+ * {@code
+ * Lens ageDobLens = Lens.lens(Person::getAge, Person::setAge);
+ *
+ * Person adult = new Person(18);
+ * Integer age = view(ageDobLens, adult); // 18
+ *
+ * Person olderAdult = set(ageDobLens, LocalDate.of(1997, 1, 1), adult);
+ * Integer olderAge = view(ageDobLens, olderAdult); // 19 at the time of this writing...anyone else feel old?
+ * }
+ * 
+ * Additionally, we might imagine a lens that produces a different "larger" value on updating than what was given. + * Consider a lens that reads the first string from a list, but produces a Set of strings on update: + *
+ * {@code
+ * Lens, Set, String, String> lens = Lens.lens(
+ *         l -> l.get(0),
+ *         (l, s) -> {
+ *             List copy = new ArrayList<>(l);
+ *             copy.set(0, s);
+ *             return new HashSet<>(copy);
+ *         });
+ *
+ * String firstElement = view(lens, asList("foo", "bar")); // "foo
+ * System.out.println(firstElement);
+ *
+ * set(lens, "oof", asList("foo", "bar")); // ["bar", "oof"]
+ * set(lens, "bar", asList("foo", "bar")); // ["bar"]
+ * }
+ * 
+ * For more information, learn + * about + * lenses. + * + * @param The type of the "larger" value for reading + * @param The type of the "larger" value for putting + * @param The type of the "smaller" value that is read + * @param The type of the "smaller" update value + */ @FunctionalInterface public interface Lens extends Functor { , FB extends Functor> FT apply(Function fn, S s); + /** + * Fix this lens against some functor, producing a non-polymorphic runnable lens as an {@link Fn2}. + *

+ * Although the Java type system does not allow enforceability, the functor instance FT should be the same as FB, + * only differentiating in their parameters. + * + * @param The type of the lifted T + * @param The type of the lifted B + * @return the lens, "fixed" to the functor + */ default , FB extends Functor> Fixed fix() { return this::apply; } @@ -25,14 +155,41 @@ default Lens fmap(Function fn) { return this.compose(Lens.lens(id(), (s, t) -> fn.apply(t))); } + /** + * Left-to-right composition of lenses. Requires compatibility between S and T. + * + * @param f the other lens + * @param the new "smaller" value to read (previously A) + * @param the new "smaller" update value (previously B) + * @return the composed lens + */ default Lens andThen(Lens f) { return f.compose(this); } + /** + * Right-to-left composition of lenses. Requires compatibility between A and B. + * + * @param g the other lens + * @param the new "larger" value for reading (previously S) + * @param the new "larger" value for putting (previously T) + * @return the composed lens + */ default Lens compose(Lens g) { return lens(view(g).fmap(view(this)), (q, b) -> over(g, set(this, b), q)); } + /** + * Static factory method for creating a lens from a getter function and a setter function. + * + * @param getter the getter function + * @param setter the setter function + * @param The type of the "larger" value for reading + * @param The type of the "larger" value for putting + * @param The type of the "smaller" value that is read + * @param The type of the "smaller" update value + * @return the lens + */ static Lens lens(Function getter, BiFunction setter) { return new Lens() { @@ -45,12 +202,28 @@ public , FB extends Functor> FT apply(Function The type of both "larger" values + * @param The type of both "smaller" values + * @return the lens + */ @SuppressWarnings("unchecked") static Lens.Simple simpleLens(Function getter, BiFunction setter) { return lens(getter, setter)::apply; } + /** + * A convenience type with a simplified type signature for common lenses with both unified "larger" values and + * unified "smaller" values. + * + * @param The type of both "larger" values + * @param The type of both "smaller" values + */ @FunctionalInterface interface Simple extends Lens { @@ -68,12 +241,31 @@ default Lens.Simple andThen(Lens.Simple f) { return f.compose(this); } + /** + * A convenience type with a simplified type signature for fixed simple lenses. + * + * @param The type of both "larger" values + * @param The type of both "smaller" values + * @param The type of the lifted s + * @param The type of the lifted A + */ @FunctionalInterface interface Fixed, FA extends Functor> extends Lens.Fixed { } } + /** + * A lens that has been fixed to a functor. Because the lens is no longer polymorphic, it can additionally be safely + * represented as an Fn2. + * + * @param The type of the "larger" value for reading + * @param The type of the "larger" value for putting + * @param The type of the "smaller" value that is read + * @param The type of the "smaller" update value + * @param The type of the lifted T + * @param The type of the lifted B + */ @FunctionalInterface interface Fixed, FB extends Functor> extends Fn2, S, FT> { diff --git a/src/main/java/com/jnape/palatable/lambda/lens/functions/Over.java b/src/main/java/com/jnape/palatable/lambda/lens/functions/Over.java index 49ff92930..782f3d5c7 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/functions/Over.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/functions/Over.java @@ -8,6 +8,21 @@ import java.util.function.Function; +/** + * Given a lens, a function from A to B, and a "larger" value S, produce a + * T by retrieving the A from the S, applying the function, and updating the + * S with the B resulting from the function. + *

+ * This function is similar to {@link Set}, except that it allows the setting value B to be derived from + * S via function application, rather than provided. + * + * @param the type of the larger value + * @param the type of the larger updated value + * @param the type of the smaller retrieving value + * @param the type of the smaller setting value + * @see Set + * @see View + */ public final class Over implements Fn3, Function, S, T> { private Over() { diff --git a/src/main/java/com/jnape/palatable/lambda/lens/functions/Set.java b/src/main/java/com/jnape/palatable/lambda/lens/functions/Set.java index ad2df38ac..aed093e8f 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/functions/Set.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/functions/Set.java @@ -7,7 +7,22 @@ import com.jnape.palatable.lambda.lens.Lens; import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; - +import static com.jnape.palatable.lambda.lens.functions.Over.over; + +/** + * Given a lens, a "smaller" value B, and a "larger" value S, produce a T by + * lifting the lens into {@link Identity}. + *

+ * More idiomatically, this function can be used to treat a lens as a "setter" of Bs on Ss, + * potentially producing a different "larger" value, T. + * + * @param the type of the larger value + * @param the type of the larger updated value + * @param the type of the smaller retrieving value (unused, but necessary for composition) + * @param the type of the smaller setting value + * @see Over + * @see View + */ public final class Set implements Fn3, B, S, T> { private Set() { @@ -15,9 +30,7 @@ private Set() { @Override public T apply(Lens lens, B b, S s) { - return lens., Identity>fix() - .apply(constantly(b).fmap(Identity::new), s) - .runIdentity(); + return over(lens, constantly(b), s); } public static Set set() { diff --git a/src/main/java/com/jnape/palatable/lambda/lens/functions/View.java b/src/main/java/com/jnape/palatable/lambda/lens/functions/View.java index 1d12df267..966debeff 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/functions/View.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/functions/View.java @@ -5,6 +5,19 @@ import com.jnape.palatable.lambda.functor.builtin.Const; import com.jnape.palatable.lambda.lens.Lens; +/** + * Given a lens and a "larger" value S, retrieve a "smaller" value A by lifting the lens into + * {@link Const}. + *

+ * More idiomatically, this function can be used to treat a lens as a "getter" of As from Ss. + * + * @param the type of the larger value + * @param the type of the larger updated value (unused, but necessary for composition) + * @param the type of the smaller retrieving value + * @param the type of the smaller setting value (unused, but necessary for composition) + * @see Set + * @see Over + */ public final class View implements Fn2, S, A> { private View() { diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/CollectionLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/CollectionLens.java index edbbd2297..5bcf16315 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/CollectionLens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/CollectionLens.java @@ -10,12 +10,35 @@ import static com.jnape.palatable.lambda.lens.Lens.simpleLens; -public class CollectionLens { +/** + * Lenses that operate on {@link Collection}s. + */ +public final class CollectionLens { + private CollectionLens() { + } + + /** + * Convenience static factory method for creating a lens that focuses on a copy of a Collection, given + * a function that creates the copy. Useful for composition to avoid mutating a Collection reference. + * + * @param copyFn the copying function + * @param the collection element type + * @param the type of the collection + * @return a lens that focuses on a copy of CX + */ public static > Lens.Simple asCopy(Function copyFn) { return simpleLens(copyFn, (__, copy) -> copy); } + /** + * Convenience static factory method for creating a lens that focuses on an arbitrary {@link Collection} as a + * {@link Set}. + * + * @param the collection element type + * @param the type of the collection + * @return a lens that focuses on a Collection as a Set + */ public static > Lens.Simple> asSet() { return simpleLens(HashSet::new, (xsL, xsS) -> { xsL.retainAll(xsS); @@ -23,6 +46,13 @@ public static > Lens.Simple> asSet() { }); } + /** + * Convenience static factory method for creating a lens that focuses on a Collection as a Stream. + * + * @param the collection element type + * @param the type of the collection + * @return a lens that focuses on a Collection as a stream. + */ public static > Lens.Simple> asStream() { return simpleLens(Collection::stream, (xsL, xsS) -> { xsL.clear(); diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/EitherLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/EitherLens.java index cb92c6dda..8652259ec 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/EitherLens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/EitherLens.java @@ -7,15 +7,36 @@ import static com.jnape.palatable.lambda.lens.Lens.simpleLens; +/** + * Lenses that operate on {@link Either}s. + */ public final class EitherLens { private EitherLens() { } + /** + * Convenience static factory method for creating a lens over right values, wrapping them in an {@link Optional}. + * When setting, an empty Optional value means to leave the either unaltered, where as a present Optional value + * replaces the either with a right over the wrapped Optional value. + * + * @param the left parameter type + * @param the right parameter type + * @return a lens that focuses on right values + */ public static Lens.Simple, Optional> right() { return simpleLens(Either::toOptional, (lOrR, optR) -> optR.>map(Either::right).orElse(lOrR)); } + /** + * Convenience static factory method for creating a lens over left values, wrapping them in an {@link Optional}. + * When setting, an empty Optional value means to leave the either unaltered, where as a present Optional value + * replaces the either with a left over the wrapped Optional value. + * + * @param the left parameter type + * @param the right parameter type + * @return a lens that focuses on left values + */ public static Lens.Simple, Optional> left() { return simpleLens(e -> e.match(Optional::ofNullable, __ -> Optional.empty()), (lOrR, optL) -> optL.>map(Either::left).orElse(lOrR)); diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/ListLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/ListLens.java index f03f2cc0b..49982aad7 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/ListLens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/ListLens.java @@ -4,24 +4,44 @@ import java.util.ArrayList; import java.util.List; +import java.util.Optional; +import static com.jnape.palatable.lambda.lens.Lens.lens; import static com.jnape.palatable.lambda.lens.Lens.simpleLens; +/** + * Lenses that operate on {@link List}s. + */ public final class ListLens { private ListLens() { } + /** + * Convenience static factory method for creating a lens over a copy of a list. Useful for composition to avoid + * mutating a list reference. + * + * @param the list element type + * @return a lens that focuses on copies of lists + */ public static Lens.Simple, List> asCopy() { return simpleLens(ArrayList::new, (xs, ys) -> ys); } - public static Lens.Simple, X> at(int index) { - return simpleLens(xs -> xs.size() > index ? xs.get(index) : null, - (xs, x) -> { - if (xs.size() > index) - xs.set(index, x); - return xs; - }); + /** + * Convenience static factory method for creating a lens that focuses on an element in a list at a particular index. + * Wraps result in an Optional to handle null values or indexes that fall outside of list boundaries. + * + * @param index the index to focus on + * @param the list element type + * @return an Optional wrapping the element at the index + */ + public static Lens, List, Optional, X> at(int index) { + return lens(xs -> Optional.ofNullable(xs.size() > index ? xs.get(index) : null), + (xs, x) -> { + if (xs.size() > index) + xs.set(index, x); + return xs; + }); } } diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java index bfc9d0730..0f3e91370 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java @@ -1,33 +1,62 @@ package com.jnape.palatable.lambda.lens.lenses; -import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.lens.Lens; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Optional; import java.util.Set; import static com.jnape.palatable.lambda.lens.Lens.lens; import static com.jnape.palatable.lambda.lens.Lens.simpleLens; import static com.jnape.palatable.lambda.lens.functions.View.view; +import static java.util.stream.Collectors.toSet; +/** + * Lenses that operate on {@link Map}s. + */ public final class MapLens { private MapLens() { } + /** + * Convenience static factory method for creating a lens that focuses on a copy of a Map. Useful for composition to + * avoid mutating a map reference. + * + * @param the key type + * @param the value type + * @return a lens that focuses on copies of maps + */ public static Lens.Simple, Map> asCopy() { return simpleLens(HashMap::new, (__, copy) -> copy); } - public static Lens.Simple, V> atKey(K k) { - return simpleLens(m -> m.get(k), (m, v) -> { + /** + * Convenience static factory method for creating a lens that focuses on a value at a key in a map, as an {@link + * Optional}. + * + * @param k the key to focus on + * @param the key type + * @param the value type + * @return a lens that focuses on the value at key, as an {@link Optional} + */ + public static Lens, Map, Optional, V> atKey(K k) { + return lens(m -> Optional.ofNullable(m.get(k)), (m, v) -> { m.put(k, v); return m; }); } + /** + * Convenience static factory method for creating a lens that focuses on the keys of a map. + * + * @param the key type + * @param the value type + * @return a lens that focuses on the keys of a map + */ public static Lens.Simple, Set> keys() { return simpleLens(Map::keySet, (m, ks) -> { Set keys = m.keySet(); @@ -38,13 +67,34 @@ public static Lens.Simple, Set> keys() { }); } - public static Lens, Map, Collection, Fn2> values() { - return lens(Map::values, (m, kvFn) -> { - m.entrySet().forEach(entry -> entry.setValue(kvFn.apply(entry.getKey(), entry.getValue()))); + /** + * Convenience static factory method for creating a lens that focuses on the values of a map. In the case of + * updating the map, only the entries with a value listed in the update collection of values are kept. + * + * @param the key type + * @param the value type + * @return a lens that focuses on the values of a map + */ + public static Lens, Map, Collection, Collection> values() { + return lens(Map::values, (m, vs) -> { + Set valueSet = new HashSet<>(vs); + Set matchingKeys = m.entrySet().stream() + .filter(kv -> valueSet.contains(kv.getValue())) + .map(Map.Entry::getKey) + .collect(toSet()); + m.keySet().retainAll(matchingKeys); return m; }); } + /** + * Convenience static factory method for creating a lens that focuses on the inverse of a map (keys and values + * swapped). In the case of multiple equal values becoming keys, the last one wins. + * + * @param the key type + * @param the value type + * @return a lens that focuses on the inverse of a map + */ public static Lens.Simple, Map> inverted() { return simpleLens(m -> { Map inverted = new HashMap<>(); diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/OptionalLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/OptionalLens.java index 88b562574..8737a8cf2 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/OptionalLens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/OptionalLens.java @@ -6,11 +6,20 @@ import static com.jnape.palatable.lambda.lens.Lens.simpleLens; +/** + * Lenses that operate on {@link Optional}s. + */ public final class OptionalLens { private OptionalLens() { } + /** + * Convenience static factory method for creating a lens that focuses on a value as an {@link Optional}. + * + * @param the value type + * @return a lens that focuses on the value as an Optional + */ public static Lens.Simple> asOptional() { return simpleLens(Optional::ofNullable, (v, optV) -> optV.orElse(v)); } diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/ListLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/ListLensTest.java index 321d6e802..2a71ae703 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/ListLensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/ListLensTest.java @@ -6,6 +6,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Optional; import static com.jnape.palatable.lambda.lens.functions.Set.set; import static com.jnape.palatable.lambda.lens.functions.View.view; @@ -41,10 +42,10 @@ public void asCopyFocusesOnListThroughCopy() { @Test public void atFocusesOnElementAtIndex() { - Lens.Simple, String> at0 = ListLens.at(0); + Lens, List, Optional, String> at0 = ListLens.at(0); - assertEquals("foo", view(at0, xs)); - assertEquals(null, view(at0, emptyList())); + assertEquals(Optional.of("foo"), view(at0, xs)); + assertEquals(Optional.empty(), view(at0, emptyList())); assertEquals(asList("quux", "bar", "baz"), set(at0, "quux", xs)); assertEquals(emptyList(), set(at0, "quux", emptyList())); } diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java index f12bb4dcb..bd05cf541 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java @@ -1,6 +1,5 @@ package com.jnape.palatable.lambda.lens.lenses; -import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.lens.Lens; import org.junit.Before; import org.junit.Test; @@ -9,6 +8,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Optional; import java.util.Set; import static com.jnape.palatable.lambda.lens.functions.Set.set; @@ -47,9 +47,9 @@ public void asCopyFocusesOnMapThroughCopy() { @Test public void atKeyFocusesOnValueAtKey() { - Lens, Map, Integer, Integer> atFoo = MapLens.atKey("foo"); + Lens, Map, Optional, Integer> atFoo = MapLens.atKey("foo"); - assertEquals((Integer) 1, view(atFoo, m)); + assertEquals(Optional.of(1), view(atFoo, m)); Map updated = set(atFoo, -1, m); assertEquals(new HashMap() {{ @@ -77,15 +77,14 @@ public void keysFocusesOnKeys() { @Test public void valuesFocusesOnValues() { - Lens, Map, Collection, Fn2> values = MapLens.values(); + Lens, Map, Collection, Collection> values = MapLens.values(); assertEquals(m.values(), view(values, m)); - Map updated = set(values, (k, v) -> k.length() + v, m); + Map updated = set(values, asList(1, 2), m); assertEquals(new HashMap() {{ - put("foo", 4); - put("bar", 5); - put("baz", 6); + put("foo", 1); + put("bar", 2); }}, updated); assertSame(m, updated); } @@ -109,5 +108,13 @@ public void invertedFocusesOnMapWithKeysAndValuesSwitched() { put("baz", 3); }}, updated); assertSame(m, updated); + + Map withDuplicateValues = new HashMap() {{ + put("foo", 1); + put("bar", 1); + }}; + assertEquals(new HashMap() {{ + put(1, "foo"); + }}, view(inverted, withDuplicateValues)); } } \ No newline at end of file From 6a12a2f81a23df3a341959fba3cdf09f1510d3e5 Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 28 Aug 2016 18:56:52 -0500 Subject: [PATCH 29/29] [maven-release-plugin] prepare release lambda-1.5 --- pom.xml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 08d34aa3d..491ad2e47 100644 --- a/pom.xml +++ b/pom.xml @@ -1,6 +1,5 @@ - + 4.0.0 @@ -10,7 +9,7 @@ lambda - 1.5-SNAPSHOT + 1.5 jar Lambda