From c4441f2c250ce31de994b7f17356ca5e0a1d46d1 Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 28 Aug 2016 18:56:56 -0500 Subject: [PATCH 1/6] [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 491ad2e47..f6bf6ed2f 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ lambda - 1.5 + 1.6-SNAPSHOT jar Lambda From a49401b63c99ce5a0e74988fca131beb41b55d71 Mon Sep 17 00:00:00 2001 From: jnape Date: Mon, 29 Aug 2016 01:58:44 -0500 Subject: [PATCH 2/6] Adding mapS - mapB on Lens for mapping specific parameters of lenses. Also adding lift and unLift variants for OptionalLens --- .../com/jnape/palatable/lambda/lens/Lens.java | 94 ++++++++++---- .../lambda/lens/lenses/OptionalLens.java | 118 ++++++++++++++++++ .../jnape/palatable/lambda/lens/LensTest.java | 15 +++ .../lambda/lens/lenses/OptionalLensTest.java | 57 +++++++++ 4 files changed, 259 insertions(+), 25 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 6717baefc..62b24ccfb 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java @@ -126,10 +126,10 @@ * 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 + * @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 { @@ -142,8 +142,8 @@ public interface Lens extends Functor { * 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 + * @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() { @@ -152,7 +152,51 @@ default , FB extends Functor> Fixed @Override default Lens fmap(Function fn) { - return this.compose(Lens.lens(id(), (s, t) -> fn.apply(t))); + return compose(Lens.lens(id(), (s, t) -> fn.apply(t))); + } + + /** + * Contravariantly map S to R, yielding a new lens. + * + * @param fn the mapping function + * @param the type of the new "larger" value for reading + * @return the new lens + */ + default Lens mapS(Function fn) { + return compose(lens(fn, (r, t) -> t)); + } + + /** + * Covariantly map T to U, yielding a new lens. + * + * @param fn the mapping function + * @param the type of the new "larger" value for putting + * @return the new lens + */ + default Lens mapT(Function fn) { + return fmap(fn); + } + + /** + * Covariantly map A to C, yielding a new lens. + * + * @param fn the mapping function + * @param the type of the new "smaller" value that is read + * @return the new lens + */ + default Lens mapA(Function fn) { + return andThen(lens(fn, (a, b) -> b)); + } + + /** + * Contravariantly map B to Z, yielding a new lens. + * + * @param fn the mapping function + * @param the type of the new "smaller" update value + * @return the new lens + */ + default Lens mapB(Function fn) { + return andThen(lens(id(), (a, z) -> fn.apply(z))); } /** @@ -184,10 +228,10 @@ default Lens compose(Lens g) { * * @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 + * @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, @@ -207,8 +251,8 @@ public , FB extends Functor> FT apply(Function The type of both "larger" values - * @param The type of both "smaller" values + * @param the type of both "larger" values + * @param the type of both "smaller" values * @return the lens */ @SuppressWarnings("unchecked") @@ -221,8 +265,8 @@ static Lens.Simple simpleLens(Function gett * 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 + * @param the type of both "larger" values + * @param the type of both "smaller" values */ @FunctionalInterface interface Simple extends Lens { @@ -244,10 +288,10 @@ default Lens.Simple andThen(Lens.Simple f) { /** * 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 + * @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> @@ -259,12 +303,12 @@ interface Fixed, FA extends Functor> * 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 + * @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> 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 8737a8cf2..ec8eba0e6 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 @@ -23,4 +23,122 @@ private OptionalLens() { public static Lens.Simple> asOptional() { return simpleLens(Optional::ofNullable, (v, optV) -> optV.orElse(v)); } + + /** + * Given a lens and a default S, lift S into Optional. + * + * @param lens the lens + * @param defaultS the S to use if an empty Optional value is given + * @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 with S lifted + */ + public static Lens, T, A, B> liftS(Lens lens, S defaultS) { + return lens.mapS(optS -> optS.orElse(defaultS)); + } + + /** + * Given a lens, lift T into Optional. + * + * @param lens the lens + * @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 with T lifted + */ + public static Lens, A, B> liftT(Lens lens) { + return lens.mapT(Optional::ofNullable); + } + + /** + * Given a lens, lift A into Optional. + * + * @param lens the lens + * @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 with A lifted + */ + public static Lens, B> liftA(Lens lens) { + return lens.mapA(Optional::ofNullable); + } + + /** + * Given a lens and a default B, lift B into Optional. + * + * @param lens the lens + * @param defaultB the B to use if an empty Optional value is given + * @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 with B lifted + */ + public static Lens> liftB(Lens lens, B defaultB) { + return lens.mapB(optB -> optB.orElse(defaultB)); + } + + /** + * Given a lens with S lifted into Optional, flatten S back down. + * + * @param lens the lens + * @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 with S flattened + */ + public static Lens unLiftS(Lens, T, A, B> lens) { + return lens.mapS(Optional::ofNullable); + } + + /** + * Given a lens with T lifted into Optional and a default T, flatten T back + * down. + * + * @param lens the lens + * @param defaultT the T to use if lens produces an empty Optional + * @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 with T flattened + */ + public static Lens unLiftT(Lens, A, B> lens, T defaultT) { + return lens.mapT(optT -> optT.orElse(defaultT)); + } + + /** + * Given a lens with A lifted into Optional and a default A, flatten A back + * down. + * + * @param lens the lens + * @param defaultA the A to use if lens produces an empty Optional + * @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 with A flattened + */ + public static Lens unLiftA(Lens, B> lens, A defaultA) { + return lens.mapA(optA -> optA.orElse(defaultA)); + } + + /** + * Given a lens with B lifted, flatten B back down. + * + * @param lens the lens + * @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 with B flattened + */ + public static Lens unLiftB(Lens> lens) { + return lens.mapB(Optional::ofNullable); + } } 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 c9deff678..c7e1419a9 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java @@ -7,11 +7,13 @@ import java.util.List; 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.functions.Set.set; import static com.jnape.palatable.lambda.lens.functions.View.view; +import static java.lang.Integer.parseInt; import static java.util.Arrays.asList; import static java.util.Collections.singleton; import static java.util.Collections.singletonList; @@ -51,6 +53,19 @@ public void functorProperties() { assertEquals(false, set(LENS.fmap(Set::isEmpty), 1, singletonList("foo"))); } + @Test + public void mapsIndividuallyOverParameters() { + Lens lens = lens(s -> s.charAt(0), (s, b) -> s.length() == b); + Lens, Optional, Optional, Optional> theGambit = lens + .mapS((Optional optS) -> optS.orElse("")) + .mapT(Optional::ofNullable) + .mapA(Optional::ofNullable) + .mapB((Optional optI) -> optI.orElse(-1)); + + Lens.Fixed, Optional, Optional, Optional, Identity>, Identity>> fixed = theGambit.fix(); + assertEquals(Optional.of(true), fixed.apply(optC -> new Identity<>(optC.map(c -> parseInt(Character.toString(c)))), Optional.of("321")).runIdentity()); + } + @Test public void composition() { Map> map = singletonMap("foo", asList("one", "two", "three")); 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 index 708a57704..b5908d600 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/OptionalLensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/OptionalLensTest.java @@ -1,16 +1,29 @@ 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.Optional; +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 com.jnape.palatable.lambda.lens.lenses.OptionalLens.liftA; +import static com.jnape.palatable.lambda.lens.lenses.OptionalLens.liftB; +import static com.jnape.palatable.lambda.lens.lenses.OptionalLens.liftS; +import static com.jnape.palatable.lambda.lens.lenses.OptionalLens.liftT; import static org.junit.Assert.assertEquals; public class OptionalLensTest { + private Lens lens; + + @Before + public void setUp() throws Exception { + lens = lens(s -> s.charAt(0), (s, b) -> s.length() == b); + } + @Test public void asOptionalWrapsValuesInOptional() { Lens.Simple> asOptional = OptionalLens.asOptional(); @@ -20,4 +33,48 @@ public void asOptionalWrapsValuesInOptional() { assertEquals("bar", set(asOptional, Optional.of("bar"), "foo")); assertEquals("foo", set(asOptional, Optional.empty(), "foo")); } + + @Test + public void liftSLiftsSToOptional() { + assertEquals((Character) '3', view(liftS(lens, "3"), Optional.empty())); + } + + @Test + public void liftTLiftsTToOptional() { + assertEquals(Optional.of(true), set(liftT(lens), 3, "123")); + } + + @Test + public void liftALiftsAToOptional() { + assertEquals(Optional.of('1'), view(liftA(lens), "123")); + } + + @Test + public void liftBLiftsBToOptional() { + assertEquals(true, set(OptionalLens.liftB(lens, 1), Optional.empty(), "1")); + } + + @Test + public void unLiftSPullsSOutOfOptional() { + Lens, Optional, Optional, Optional> liftedToOptional = liftS(liftT(liftA(liftB(lens, 3))), "123"); + assertEquals(Optional.of('f'), view(OptionalLens.unLiftS(liftedToOptional), "f")); + } + + @Test + public void unLiftTPullsTOutOfOptional() { + Lens, Optional, Optional, Optional> liftedToOptional = liftS(liftT(liftA(liftB(lens, 3))), "123"); + assertEquals(true, set(OptionalLens.unLiftT(liftedToOptional, false), Optional.of(3), Optional.of("321"))); + } + + @Test + public void unLiftAPullsAOutOfOptional() { + Lens, Optional, Optional, Optional> liftedToOptional = liftS(liftT(liftA(liftB(lens, 3))), "123"); + assertEquals((Character) '1', view(OptionalLens.unLiftA(liftedToOptional, '4'), Optional.empty())); + } + + @Test + public void unLiftBPullsBOutOfOptional() { + Lens, Optional, Optional, Optional> liftedToOptional = liftS(liftT(liftA(liftB(lens, 3))), "123"); + assertEquals(Optional.of(true), set(OptionalLens.unLiftB(liftedToOptional), 3, Optional.of("321"))); + } } \ No newline at end of file From a44359ba97c98652bc48c2fc9d07e86c501f50e6 Mon Sep 17 00:00:00 2001 From: John Napier Date: Mon, 29 Aug 2016 02:04:52 -0500 Subject: [PATCH 3/6] Adding brief lens section and fixing code indentitation --- README.md | 223 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 141 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index 57c889944..b443810ef 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Functional patterns for Java 8 - [Tuples](#tuples) - [HMaps](#hmaps) - [Either](#either) + - [Lenses](#lenses) - [Notes](#notes) - [License](#license) @@ -43,17 +44,17 @@ Add the following dependency to your: `pom.xml` ([Maven](https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html)): ```xml - - com.jnape.palatable - lambda - 1.3 - + + com.jnape.palatable + lambda + 1.5 + ``` `build.gradle` ([Gradle](https://docs.gradle.org/current/userguide/dependency_management.html)): ```gradle - compile group: 'com.jnape.palatable', name: 'lambda', version: '1.3' +compile group: 'com.jnape.palatable', name: 'lambda', version: '1.5' ``` @@ -62,77 +63,77 @@ Add the following dependency to your: First, the obligatory `map`/`filter`/`reduce` example: ```Java - Integer sumOfEvenIncrements = - reduceLeft((x, y) -> x + y, - filter(x -> x % 2 == 0, - map(x -> x + 1, asList(1, 2, 3, 4, 5)))); - //-> 12 +Integer sumOfEvenIncrements = + reduceLeft((x, y) -> x + y, + filter(x -> x % 2 == 0, + map(x -> x + 1, asList(1, 2, 3, 4, 5)))); +//-> 12 ``` Every function in lambda is [curried](https://www.wikiwand.com/en/Currying), so we could have also done this: ```Java - Fn1, Integer> sumOfEvenIncrementsFn = - map((Integer x) -> x + 1) - .then(filter(x -> x % 2 == 0)) - .then(reduceLeft((x, y) -> x + y)); - - Integer sumOfEvenIncrements = sumOfEvenIncrementsFn.apply(asList(1, 2, 3, 4, 5)); - //-> 12 +Fn1, Integer> sumOfEvenIncrementsFn = + map((Integer x) -> x + 1) + .then(filter(x -> x % 2 == 0)) + .then(reduceLeft((x, y) -> x + y)); + +Integer sumOfEvenIncrements = sumOfEvenIncrementsFn.apply(asList(1, 2, 3, 4, 5)); +//-> 12 ``` How about the positive squares below 100: ```Java - Iterable positiveSquaresBelow100 = - takeWhile(x -> x < 100, map(x -> x * x, iterate(x -> x + 1, 1))); - //-> [1, 4, 9, 16, 25, 36, 49, 64, 81] +Iterable positiveSquaresBelow100 = + takeWhile(x -> x < 100, map(x -> x * x, iterate(x -> x + 1, 1))); +//-> [1, 4, 9, 16, 25, 36, 49, 64, 81] ``` We could have also used `unfoldr`: ```Java - Iterable positiveSquaresBelow100 = unfoldr(x -> { - int square = x * x; - return square < 100 ? Optional.of(tuple(square, x + 1)) : Optional.empty(); - }, 1); - //-> [1, 4, 9, 16, 25, 36, 49, 64, 81] +Iterable positiveSquaresBelow100 = unfoldr(x -> { + int square = x * x; + return square < 100 ? Optional.of(tuple(square, x + 1)) : Optional.empty(); + }, 1); +//-> [1, 4, 9, 16, 25, 36, 49, 64, 81] ``` What if we want the cross product of a domain and codomain: ```Java - Iterable> crossProduct = - take(10, cartesianProduct(asList(1, 2, 3), asList("a", "b", "c"))); - //-> (1,"a"), (1,"b"), (1,"c"), (2,"a"), (2,"b"), (2,"c"), (3,"a"), (3,"b"), (3,"c") +Iterable> crossProduct = + take(10, cartesianProduct(asList(1, 2, 3), asList("a", "b", "c"))); +//-> (1,"a"), (1,"b"), (1,"c"), (2,"a"), (2,"b"), (2,"c"), (3,"a"), (3,"b"), (3,"c") ``` Let's compose two functions: ```Java - Fn1 add = x -> x + 1; - Fn1 subtract = x -> x -1; +Fn1 add = x -> x + 1; +Fn1 subtract = x -> x -1; - Fn1 noOp = add.then(subtract); - // same as - Fn1 alsoNoOp = subtract.compose(subtract); +Fn1 noOp = add.then(subtract); +// same as +Fn1 alsoNoOp = subtract.compose(subtract); ``` And partially apply some: ```Java - Fn2 add = (x, y) -> x + y; +Fn2 add = (x, y) -> x + y; - Fn1 add1 = add.apply(1); - add1.apply(2); - //-> 3 +Fn1 add1 = add.apply(1); +add1.apply(2); +//-> 3 ``` And have fun with 3s: ```Java - Iterable> multiplesOf3InGroupsOf3 = - take(10, inGroupsOf(3, unfoldr(x -> Optional.of(tuple(x * 3, x + 1)), 1))); - //-> [[3, 6, 9], [12, 15, 18], [21, 24, 27]] +Iterable> multiplesOf3InGroupsOf3 = + take(10, inGroupsOf(3, unfoldr(x -> Optional.of(tuple(x * 3, x + 1)), 1))); +//-> [[3, 6, 9], [12, 15, 18], [21, 24, 27]] ``` Check out the [tests](https://github.com/palatable/lambda/tree/master/src/test/java/com/jnape/palatable/lambda/functions/builtin) or [javadoc](http://palatable.github.io/lambda/javadoc/) for more examples. @@ -149,13 +150,13 @@ HLists are type-safe heterogeneous lists, meaning they can store elements of dif The following illustrates how the linear expansion of the recursive type signature for `HList` prevents ill-typed expressions: ```Java - HCons> hList = HList.cons(1, HList.cons("foo", HList.nil())); +HCons> hList = HList.cons(1, HList.cons("foo", HList.nil())); - System.out.println(hList.head()); // prints 1 - System.out.println(hList.tail().head()); // prints "foo" +System.out.println(hList.head()); // prints 1 +System.out.println(hList.tail().head()); // prints "foo" - HNil nil = hList.tail().tail(); - //nil.head() won't type-check +HNil nil = hList.tail().tail(); +//nil.head() won't type-check ``` #### Tuples @@ -165,40 +166,40 @@ One of the primary downsides to using `HList`s in Java is how quickly the type s To address this, tuples in lambda are specializations of `HList`s up to 5 elements deep, with added support for index-based accessor methods. ```Java - HNil nil = HList.nil(); - SingletonHList singleton = nil.cons(5); - Tuple2 tuple2 = singleton.cons(4); - Tuple3 tuple3 = tuple2.cons(3); - Tuple4 tuple4 = tuple3.cons(2); - Tuple5 tuple5 = tuple4.cons(1); - - System.out.println(tuple2._1()); // prints 4 - System.out.println(tuple5._5()); // prints 5 +HNil nil = HList.nil(); +SingletonHList singleton = nil.cons(5); +Tuple2 tuple2 = singleton.cons(4); +Tuple3 tuple3 = tuple2.cons(3); +Tuple4 tuple4 = tuple3.cons(2); +Tuple5 tuple5 = tuple4.cons(1); + +System.out.println(tuple2._1()); // prints 4 +System.out.println(tuple5._5()); // prints 5 ``` Additionally, `HList` provides convenience static factory methods for directly constructing lists of up to 5 elements: ```Java - SingletonHList singleton = HList.singletonHList(1); - Tuple2 tuple2 = HList.tuple(1, 2); - Tuple3 tuple3 = HList.tuple(1, 2, 3); - Tuple4 tuple4 = HList.tuple(1, 2, 3, 4); - Tuple5 tuple5 = HList.tuple(1, 2, 3, 4, 5); +SingletonHList singleton = HList.singletonHList(1); +Tuple2 tuple2 = HList.tuple(1, 2); +Tuple3 tuple3 = HList.tuple(1, 2, 3); +Tuple4 tuple4 = HList.tuple(1, 2, 3, 4); +Tuple5 tuple5 = HList.tuple(1, 2, 3, 4, 5); ``` Finally, all `Tuple*` classes are instances of both `Functor` and `Bifunctor`: ```Java - Tuple2 mappedTuple2 = tuple(1, 2).biMap(x -> x + 1, Object::toString); +Tuple2 mappedTuple2 = tuple(1, 2).biMap(x -> x + 1, Object::toString); - System.out.println(mappedTuple2._1()); // prints 2 - System.out.println(mappedTuple2._2()); // prints "2" +System.out.println(mappedTuple2._1()); // prints 2 +System.out.println(mappedTuple2._2()); // prints "2" - Tuple3 mappedTuple3 = tuple("foo", true, 1).biMap(x -> !x, x -> x + 1); +Tuple3 mappedTuple3 = tuple("foo", true, 1).biMap(x -> !x, x -> x + 1); - System.out.println(mappedTuple3._1()); // prints "foo" - System.out.println(mappedTuple3._2()); // prints false - System.out.println(mappedTuple3._3()); // prints 2 +System.out.println(mappedTuple3._1()); // prints "foo" +System.out.println(mappedTuple3._2()); // prints false +System.out.println(mappedTuple3._3()); // prints 2 ``` ### Heterogeneous Maps @@ -206,14 +207,14 @@ Finally, all `Tuple*` classes are instances of both `Functor` and `Bifunctor`: HMaps are type-safe heterogeneous maps, meaning they can store mappings to different value types in the same map; however, whereas HLists encode value types in their type signatures, HMaps rely on the keys to encode the value type that they point to. ```Java - TypeSafeKey stringKey = TypeSafeKey.typeSafeKey(); - TypeSafeKey intKey = TypeSafeKey.typeSafeKey(); - HMap hmap = HMap.hMap(stringKey, "string value", - intKey, 1); - - Optional stringValue = hmap.get(stringKey); // Optional["string value"] - Optional intValue = hmap.get(intKey); // Optional[1] - Optional anotherIntValue = hmap.get(anotherIntKey); // Optional.empty +TypeSafeKey stringKey = TypeSafeKey.typeSafeKey(); +TypeSafeKey intKey = TypeSafeKey.typeSafeKey(); +HMap hmap = HMap.hMap(stringKey, "string value", + intKey, 1); + +Optional stringValue = hmap.get(stringKey); // Optional["string value"] +Optional intValue = hmap.get(intKey); // Optional[1] +Optional anotherIntValue = hmap.get(anotherIntKey); // Optional.empty ``` ### Either @@ -223,18 +224,76 @@ Binary tagged unions are represented as `Either`s, which resolve to one of Rather than supporting explicit value unwrapping, `Either` supports many useful comprehensions to help facilitate type-safe interactions. For example, `Either#match` is used to resolve an `Either` to a different type. ```Java - Either right = Either.right(1); - Either left = Either.left("Head fell off"); +Either right = Either.right(1); +Either left = Either.left("Head fell off"); - Boolean successful = right.match(l -> false, r -> true); - //-> true - - List values = left.match(l -> Collections.emptyList(), Collections::singletonList); - //-> [] +Boolean successful = right.match(l -> false, r -> true); +//-> true + +List values = left.match(l -> Collections.emptyList(), Collections::singletonList); +//-> [] ``` Check out the tests for [more examples](https://github.com/palatable/lambda/blob/master/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java) of ways to interact with `Either`. +Lenses +---- + +Lambda also ships with a first-class lens type, as well as a small library of useful general lenses: + +```Java +Lens, List, Optional, String> stringAt0 = ListLens.at(0); + +List strings = asList("foo", "bar", "baz"); +view(stringAt0, strings); // Optional[foo] +set(stringAt0, "quux", strings); // [quux, bar, baz] +over(stringAt0, s -> s.map(String::toUpperCase).orElse(""), strings); // [FOO, bar, baz] +``` + +There are three functions that lambda provides that interface directly with lenses: `view`, `over`, and `set`. As the name implies, `view` and `set` are used to retrieve values and store values, respectively, whereas `over` is used to apply a function to the value a lens is focused on, alter it, and store it (you can think of `set` as a specialization of `over` using `constantly`). + +Lenses can be easily created. Consider the following `Person` class: + +```Java +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())); + } +} +``` + +...and a lens for getting and setting `age` as an `int`: + +```Java +Lens ageLensWithInt = Lens.lens(Person::getAge, Person::setAge); + +//or, when each pair of type arguments match... + +Lens.Simple alsoAgeLensWithInt = Lens.lens(Person::getAge, Person::setAge); +``` + +If we wanted a lens for the `LocalDate` version of `setAge`, we could use the same method references and only alter the type signature: + +```Java +Lens ageLensWithLocalDate = Lens.lens(Person::getAge, Person::setAge); +``` + +Lenses also compose, can have any parameter independently mapped over, and support various other properties that make them interesting and useful. Check out the tests or the javadocs for more info. + Notes ----- From 60310c01796a9e761f01886d2e0d0065e28bc337 Mon Sep 17 00:00:00 2001 From: John Napier Date: Tue, 30 Aug 2016 20:17:26 -0500 Subject: [PATCH 4/6] Touching maven version badge to get the latest one --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b443810ef..295e19035 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ λ ====== [![Build Status](https://travis-ci.org/palatable/lambda.svg)](https://travis-ci.org/palatable/lambda) -[![Lambda](https://img.shields.io/maven-central/v/com.jnape.palatable/lambda.svg?maxAge=2592000)](http://search.maven.org/#search%7Cga%7C1%7Ccom.jnape.palatable.lambda) +[![Lambda](https://img.shields.io/maven-central/v/com.jnape.palatable/lambda.svg)](http://search.maven.org/#search%7Cga%7C1%7Ccom.jnape.palatable.lambda) Functional patterns for Java 8 From 1e4bbbaa1c9cb786f42825b3c5130429d01f988a Mon Sep 17 00:00:00 2001 From: jnape Date: Tue, 30 Aug 2016 22:42:38 -0500 Subject: [PATCH 5/6] Adding default value overrides for Optional returning lenses in ListLens and MapLens; updating README --- README.md | 25 +++++++++++++++++-- .../lambda/lens/lenses/ListLens.java | 15 +++++++++++ .../palatable/lambda/lens/lenses/MapLens.java | 16 ++++++++++++ .../lambda/lens/lenses/ListLensTest.java | 10 ++++++++ .../lambda/lens/lenses/MapLensTest.java | 17 +++++++++++++ 5 files changed, 81 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 295e19035..60e512918 100644 --- a/README.md +++ b/README.md @@ -283,7 +283,7 @@ Lens ageLensWithInt = Lens.lens(Person::getAge //or, when each pair of type arguments match... -Lens.Simple alsoAgeLensWithInt = Lens.lens(Person::getAge, Person::setAge); +Lens.Simple alsoAgeLensWithInt = Lens.simpleLens(Person::getAge, Person::setAge); ``` If we wanted a lens for the `LocalDate` version of `setAge`, we could use the same method references and only alter the type signature: @@ -292,7 +292,28 @@ If we wanted a lens for the `LocalDate` version of `setAge`, we could use the sa Lens ageLensWithLocalDate = Lens.lens(Person::getAge, Person::setAge); ``` -Lenses also compose, can have any parameter independently mapped over, and support various other properties that make them interesting and useful. Check out the tests or the javadocs for more info. +Compatible lenses can be trivially composed: + +```Java +Lens, List, Optional, Integer> at0 = ListLens.at(0); +Lens>, Map>, List, List> atFoo = MapLens.atKey("foo", emptyList()); + +view(atFoo.andThen(at0), singletonMap("foo", asList(1, 2, 3))); // Optional[1] +``` + +Lens provides independent `map` operations for each parameter, so incompatible lenses can also be composed: + +```Java +Lens, List, Optional, Integer> at0 = ListLens.at(0); +Lens>, Map>, Optional>, List> atFoo = MapLens.atKey("foo"); +Lens>, Map>, Optional, Integer> composed = + atFoo.mapA(optL -> optL.orElse(singletonList(-1))) + .andThen(at0); + +view(composed, singletonMap("foo", emptyList())); // Optional.empty +``` + +Check out the tests or the [javadoc](http://palatable.github.io/lambda/javadoc/) for more info. Notes ----- 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 49982aad7..009c26f18 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 @@ -8,6 +8,7 @@ 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.lenses.OptionalLens.unLiftA; /** * Lenses that operate on {@link List}s. @@ -44,4 +45,18 @@ public static Lens, List, Optional, X> at(int index) { return xs; }); } + + /** + * Convenience static factory method for creating a lens that focuses on an element in a list at a particular index, + * returning defaultValue if there is no value at that index. + * + * @param index the index to focus on + * @param defaultValue the value to use if there is no element at index + * @param the list element type + * @return the element at the index, or defaultValue + */ + @SuppressWarnings("unchecked") + public static Lens.Simple, X> at(int index, X defaultValue) { + return unLiftA(at(index), defaultValue)::apply; + } } 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 0f3e91370..6e18502aa 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 @@ -12,6 +12,7 @@ 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 com.jnape.palatable.lambda.lens.lenses.OptionalLens.unLiftA; import static java.util.stream.Collectors.toSet; /** @@ -50,6 +51,21 @@ public static Lens, Map, Optional, V> atKey(K k) { }); } + /** + * Convenience static factory method for creating a lens that focuses on a value at a key in a map, falling back to + * defaultV if the value is missing. + * + * @param k the key to focus on + * @param defaultValue the default value to use in case of a missing value at key + * @param the key type + * @param the value type + * @return a lens that focuses on the value at the key + */ + @SuppressWarnings("unchecked") + public static Lens.Simple, V> atKey(K k, V defaultValue) { + return unLiftA(atKey(k), defaultValue)::apply; + } + /** * Convenience static factory method for creating a lens that focuses on the keys of a map. * 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 2a71ae703..e01373f77 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 @@ -49,4 +49,14 @@ public void atFocusesOnElementAtIndex() { assertEquals(asList("quux", "bar", "baz"), set(at0, "quux", xs)); assertEquals(emptyList(), set(at0, "quux", emptyList())); } + + @Test + public void atWithDefaultValueFocusesOnElementAtIndex() { + Lens, List, String, String> at0 = ListLens.at(0, "missing"); + + assertEquals("foo", view(at0, xs)); + assertEquals("missing", 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/MapLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java index bd05cf541..e89c7582c 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 @@ -15,6 +15,7 @@ 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.emptyMap; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; @@ -60,6 +61,22 @@ public void atKeyFocusesOnValueAtKey() { assertSame(m, updated); } + @Test + public void atKeyWithDefaultValueFocusedOnValueAtKey() { + Lens, Map, Integer, Integer> atFoo = MapLens.atKey("foo", -1); + + assertEquals((Integer) 1, view(atFoo, m)); + assertEquals((Integer) (-1), view(atFoo, emptyMap())); + + Map updated = set(atFoo, 11, m); + assertEquals(new HashMap() {{ + put("foo", 11); + put("bar", 2); + put("baz", 3); + }}, updated); + assertSame(m, updated); + } + @Test public void keysFocusesOnKeys() { Lens, Map, Set, Set> keys = keys(); From 00727ff7c1911e28b6eb752af2f73507b8ba325a Mon Sep 17 00:00:00 2001 From: jnape Date: Tue, 30 Aug 2016 23:32:48 -0500 Subject: [PATCH 6/6] [maven-release-plugin] prepare release lambda-1.5.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f6bf6ed2f..7d79c9c8a 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ lambda - 1.6-SNAPSHOT + 1.5.1 jar Lambda