Skip to content

Commit a44359b

Browse files
authored
Adding brief lens section and fixing code indentitation
1 parent a49401b commit a44359b

1 file changed

Lines changed: 141 additions & 82 deletions

File tree

README.md

Lines changed: 141 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Functional patterns for Java 8
1515
- [Tuples](#tuples)
1616
- [HMaps](#hmaps)
1717
- [Either](#either)
18+
- [Lenses](#lenses)
1819
- [Notes](#notes)
1920
- [License](#license)
2021

@@ -43,17 +44,17 @@ Add the following dependency to your:
4344
`pom.xml` ([Maven](https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html)):
4445

4546
```xml
46-
<dependency>
47-
<groupId>com.jnape.palatable</groupId>
48-
<artifactId>lambda</artifactId>
49-
<version>1.3</version>
50-
</dependency>
47+
<dependency>
48+
<groupId>com.jnape.palatable</groupId>
49+
<artifactId>lambda</artifactId>
50+
<version>1.5</version>
51+
</dependency>
5152
```
5253

5354
`build.gradle` ([Gradle](https://docs.gradle.org/current/userguide/dependency_management.html)):
5455

5556
```gradle
56-
compile group: 'com.jnape.palatable', name: 'lambda', version: '1.3'
57+
compile group: 'com.jnape.palatable', name: 'lambda', version: '1.5'
5758
```
5859

5960

@@ -62,77 +63,77 @@ Add the following dependency to your:
6263

6364
First, the obligatory `map`/`filter`/`reduce` example:
6465
```Java
65-
Integer sumOfEvenIncrements =
66-
reduceLeft((x, y) -> x + y,
67-
filter(x -> x % 2 == 0,
68-
map(x -> x + 1, asList(1, 2, 3, 4, 5))));
69-
//-> 12
66+
Integer sumOfEvenIncrements =
67+
reduceLeft((x, y) -> x + y,
68+
filter(x -> x % 2 == 0,
69+
map(x -> x + 1, asList(1, 2, 3, 4, 5))));
70+
//-> 12
7071
```
7172

7273
Every function in lambda is [curried](https://www.wikiwand.com/en/Currying), so we could have also done this:
7374
```Java
74-
Fn1<Iterable<Integer>, Integer> sumOfEvenIncrementsFn =
75-
map((Integer x) -> x + 1)
76-
.then(filter(x -> x % 2 == 0))
77-
.then(reduceLeft((x, y) -> x + y));
78-
79-
Integer sumOfEvenIncrements = sumOfEvenIncrementsFn.apply(asList(1, 2, 3, 4, 5));
80-
//-> 12
75+
Fn1<Iterable<Integer>, Integer> sumOfEvenIncrementsFn =
76+
map((Integer x) -> x + 1)
77+
.then(filter(x -> x % 2 == 0))
78+
.then(reduceLeft((x, y) -> x + y));
79+
80+
Integer sumOfEvenIncrements = sumOfEvenIncrementsFn.apply(asList(1, 2, 3, 4, 5));
81+
//-> 12
8182
```
8283

8384
How about the positive squares below 100:
8485

8586
```Java
86-
Iterable<Integer> positiveSquaresBelow100 =
87-
takeWhile(x -> x < 100, map(x -> x * x, iterate(x -> x + 1, 1)));
88-
//-> [1, 4, 9, 16, 25, 36, 49, 64, 81]
87+
Iterable<Integer> positiveSquaresBelow100 =
88+
takeWhile(x -> x < 100, map(x -> x * x, iterate(x -> x + 1, 1)));
89+
//-> [1, 4, 9, 16, 25, 36, 49, 64, 81]
8990
```
9091

9192
We could have also used `unfoldr`:
9293

9394
```Java
94-
Iterable<Integer> positiveSquaresBelow100 = unfoldr(x -> {
95-
int square = x * x;
96-
return square < 100 ? Optional.of(tuple(square, x + 1)) : Optional.empty();
97-
}, 1);
98-
//-> [1, 4, 9, 16, 25, 36, 49, 64, 81]
95+
Iterable<Integer> positiveSquaresBelow100 = unfoldr(x -> {
96+
int square = x * x;
97+
return square < 100 ? Optional.of(tuple(square, x + 1)) : Optional.empty();
98+
}, 1);
99+
//-> [1, 4, 9, 16, 25, 36, 49, 64, 81]
99100
```
100101

101102
What if we want the cross product of a domain and codomain:
102103

103104
```Java
104-
Iterable<Tuple2<Integer, String>> crossProduct =
105-
take(10, cartesianProduct(asList(1, 2, 3), asList("a", "b", "c")));
106-
//-> (1,"a"), (1,"b"), (1,"c"), (2,"a"), (2,"b"), (2,"c"), (3,"a"), (3,"b"), (3,"c")
105+
Iterable<Tuple2<Integer, String>> crossProduct =
106+
take(10, cartesianProduct(asList(1, 2, 3), asList("a", "b", "c")));
107+
//-> (1,"a"), (1,"b"), (1,"c"), (2,"a"), (2,"b"), (2,"c"), (3,"a"), (3,"b"), (3,"c")
107108
```
108109

109110
Let's compose two functions:
110111

111112
```Java
112-
Fn1<Integer, Integer> add = x -> x + 1;
113-
Fn1<Integer, Integer> subtract = x -> x -1;
113+
Fn1<Integer, Integer> add = x -> x + 1;
114+
Fn1<Integer, Integer> subtract = x -> x -1;
114115

115-
Fn1<Integer, Integer> noOp = add.then(subtract);
116-
// same as
117-
Fn1<Integer, Integer> alsoNoOp = subtract.compose(subtract);
116+
Fn1<Integer, Integer> noOp = add.then(subtract);
117+
// same as
118+
Fn1<Integer, Integer> alsoNoOp = subtract.compose(subtract);
118119
```
119120

120121
And partially apply some:
121122

122123
```Java
123-
Fn2<Integer, Integer, Integer> add = (x, y) -> x + y;
124+
Fn2<Integer, Integer, Integer> add = (x, y) -> x + y;
124125

125-
Fn1<Integer, Integer> add1 = add.apply(1);
126-
add1.apply(2);
127-
//-> 3
126+
Fn1<Integer, Integer> add1 = add.apply(1);
127+
add1.apply(2);
128+
//-> 3
128129
```
129130

130131
And have fun with 3s:
131132

132133
```Java
133-
Iterable<Iterable<Integer>> multiplesOf3InGroupsOf3 =
134-
take(10, inGroupsOf(3, unfoldr(x -> Optional.of(tuple(x * 3, x + 1)), 1)));
135-
//-> [[3, 6, 9], [12, 15, 18], [21, 24, 27]]
134+
Iterable<Iterable<Integer>> multiplesOf3InGroupsOf3 =
135+
take(10, inGroupsOf(3, unfoldr(x -> Optional.of(tuple(x * 3, x + 1)), 1)));
136+
//-> [[3, 6, 9], [12, 15, 18], [21, 24, 27]]
136137
```
137138

138139
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
149150
The following illustrates how the linear expansion of the recursive type signature for `HList` prevents ill-typed expressions:
150151

151152
```Java
152-
HCons<Integer, HCons<String, HNil>> hList = HList.cons(1, HList.cons("foo", HList.nil()));
153+
HCons<Integer, HCons<String, HNil>> hList = HList.cons(1, HList.cons("foo", HList.nil()));
153154

154-
System.out.println(hList.head()); // prints 1
155-
System.out.println(hList.tail().head()); // prints "foo"
155+
System.out.println(hList.head()); // prints 1
156+
System.out.println(hList.tail().head()); // prints "foo"
156157

157-
HNil nil = hList.tail().tail();
158-
//nil.head() won't type-check
158+
HNil nil = hList.tail().tail();
159+
//nil.head() won't type-check
159160
```
160161

161162
#### <a name="tuples">Tuples</a>
@@ -165,55 +166,55 @@ One of the primary downsides to using `HList`s in Java is how quickly the type s
165166
To address this, tuples in lambda are specializations of `HList`s up to 5 elements deep, with added support for index-based accessor methods.
166167

167168
```Java
168-
HNil nil = HList.nil();
169-
SingletonHList<Integer> singleton = nil.cons(5);
170-
Tuple2<Integer, Integer> tuple2 = singleton.cons(4);
171-
Tuple3<Integer, Integer, Integer> tuple3 = tuple2.cons(3);
172-
Tuple4<Integer, Integer, Integer, Integer> tuple4 = tuple3.cons(2);
173-
Tuple5<Integer, Integer, Integer, Integer, Integer> tuple5 = tuple4.cons(1);
174-
175-
System.out.println(tuple2._1()); // prints 4
176-
System.out.println(tuple5._5()); // prints 5
169+
HNil nil = HList.nil();
170+
SingletonHList<Integer> singleton = nil.cons(5);
171+
Tuple2<Integer, Integer> tuple2 = singleton.cons(4);
172+
Tuple3<Integer, Integer, Integer> tuple3 = tuple2.cons(3);
173+
Tuple4<Integer, Integer, Integer, Integer> tuple4 = tuple3.cons(2);
174+
Tuple5<Integer, Integer, Integer, Integer, Integer> tuple5 = tuple4.cons(1);
175+
176+
System.out.println(tuple2._1()); // prints 4
177+
System.out.println(tuple5._5()); // prints 5
177178
```
178179

179180
Additionally, `HList` provides convenience static factory methods for directly constructing lists of up to 5 elements:
180181

181182
```Java
182-
SingletonHList<Integer> singleton = HList.singletonHList(1);
183-
Tuple2<Integer, Integer> tuple2 = HList.tuple(1, 2);
184-
Tuple3<Integer, Integer, Integer> tuple3 = HList.tuple(1, 2, 3);
185-
Tuple4<Integer, Integer, Integer, Integer> tuple4 = HList.tuple(1, 2, 3, 4);
186-
Tuple5<Integer, Integer, Integer, Integer, Integer> tuple5 = HList.tuple(1, 2, 3, 4, 5);
183+
SingletonHList<Integer> singleton = HList.singletonHList(1);
184+
Tuple2<Integer, Integer> tuple2 = HList.tuple(1, 2);
185+
Tuple3<Integer, Integer, Integer> tuple3 = HList.tuple(1, 2, 3);
186+
Tuple4<Integer, Integer, Integer, Integer> tuple4 = HList.tuple(1, 2, 3, 4);
187+
Tuple5<Integer, Integer, Integer, Integer, Integer> tuple5 = HList.tuple(1, 2, 3, 4, 5);
187188
```
188189

189190
Finally, all `Tuple*` classes are instances of both `Functor` and `Bifunctor`:
190191

191192
```Java
192-
Tuple2<Integer, String> mappedTuple2 = tuple(1, 2).biMap(x -> x + 1, Object::toString);
193+
Tuple2<Integer, String> mappedTuple2 = tuple(1, 2).biMap(x -> x + 1, Object::toString);
193194

194-
System.out.println(mappedTuple2._1()); // prints 2
195-
System.out.println(mappedTuple2._2()); // prints "2"
195+
System.out.println(mappedTuple2._1()); // prints 2
196+
System.out.println(mappedTuple2._2()); // prints "2"
196197

197-
Tuple3<String, Boolean, Integer> mappedTuple3 = tuple("foo", true, 1).biMap(x -> !x, x -> x + 1);
198+
Tuple3<String, Boolean, Integer> mappedTuple3 = tuple("foo", true, 1).biMap(x -> !x, x -> x + 1);
198199

199-
System.out.println(mappedTuple3._1()); // prints "foo"
200-
System.out.println(mappedTuple3._2()); // prints false
201-
System.out.println(mappedTuple3._3()); // prints 2
200+
System.out.println(mappedTuple3._1()); // prints "foo"
201+
System.out.println(mappedTuple3._2()); // prints false
202+
System.out.println(mappedTuple3._3()); // prints 2
202203
```
203204

204205
### <a name="hmaps">Heterogeneous Maps</a>
205206

206207
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.
207208

208209
```Java
209-
TypeSafeKey<String> stringKey = TypeSafeKey.typeSafeKey();
210-
TypeSafeKey<Integer> intKey = TypeSafeKey.typeSafeKey();
211-
HMap hmap = HMap.hMap(stringKey, "string value",
212-
intKey, 1);
213-
214-
Optional<String> stringValue = hmap.get(stringKey); // Optional["string value"]
215-
Optional<Integer> intValue = hmap.get(intKey); // Optional[1]
216-
Optional<Integer> anotherIntValue = hmap.get(anotherIntKey); // Optional.empty
210+
TypeSafeKey<String> stringKey = TypeSafeKey.typeSafeKey();
211+
TypeSafeKey<Integer> intKey = TypeSafeKey.typeSafeKey();
212+
HMap hmap = HMap.hMap(stringKey, "string value",
213+
intKey, 1);
214+
215+
Optional<String> stringValue = hmap.get(stringKey); // Optional["string value"]
216+
Optional<Integer> intValue = hmap.get(intKey); // Optional[1]
217+
Optional<Integer> anotherIntValue = hmap.get(anotherIntKey); // Optional.empty
217218
```
218219

219220
### <a name="either">Either</a>
@@ -223,18 +224,76 @@ Binary tagged unions are represented as `Either<L, R>`s, which resolve to one of
223224
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<L,R>` to a different type.
224225

225226
```Java
226-
Either<String, Integer> right = Either.right(1);
227-
Either<String, Integer> left = Either.left("Head fell off");
227+
Either<String, Integer> right = Either.right(1);
228+
Either<String, Integer> left = Either.left("Head fell off");
228229

229-
Boolean successful = right.match(l -> false, r -> true);
230-
//-> true
231-
232-
List<Integer> values = left.match(l -> Collections.emptyList(), Collections::singletonList);
233-
//-> []
230+
Boolean successful = right.match(l -> false, r -> true);
231+
//-> true
232+
233+
List<Integer> values = left.match(l -> Collections.emptyList(), Collections::singletonList);
234+
//-> []
234235
```
235236

236237
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`.
237238

239+
<a name="lenses">Lenses</a>
240+
----
241+
242+
Lambda also ships with a first-class <a href="https://www.youtube.com/watch?v=cefnmjtAolY&feature=youtu.be&hd=1">lens</a> type, as well as a small library of useful general lenses:
243+
244+
```Java
245+
Lens<List<String>, List<String>, Optional<String>, String> stringAt0 = ListLens.at(0);
246+
247+
List<String> strings = asList("foo", "bar", "baz");
248+
view(stringAt0, strings); // Optional[foo]
249+
set(stringAt0, "quux", strings); // [quux, bar, baz]
250+
over(stringAt0, s -> s.map(String::toUpperCase).orElse(""), strings); // [FOO, bar, baz]
251+
```
252+
253+
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`).
254+
255+
Lenses can be easily created. Consider the following `Person` class:
256+
257+
```Java
258+
public final class Person {
259+
private final int age;
260+
261+
public Person(int age) {
262+
this.age = age;
263+
}
264+
265+
public int getAge() {
266+
return age;
267+
}
268+
269+
public Person setAge(int age) {
270+
return new Person(age);
271+
}
272+
273+
public Person setAge(LocalDate dob) {
274+
return setAge((int) YEARS.between(dob, LocalDate.now()));
275+
}
276+
}
277+
```
278+
279+
...and a lens for getting and setting `age` as an `int`:
280+
281+
```Java
282+
Lens<Person, Person, Integer, Integer> ageLensWithInt = Lens.lens(Person::getAge, Person::setAge);
283+
284+
//or, when each pair of type arguments match...
285+
286+
Lens.Simple<Person, Integer> alsoAgeLensWithInt = Lens.lens(Person::getAge, Person::setAge);
287+
```
288+
289+
If we wanted a lens for the `LocalDate` version of `setAge`, we could use the same method references and only alter the type signature:
290+
291+
```Java
292+
Lens<Person, Person, Integer, LocalDate> ageLensWithLocalDate = Lens.lens(Person::getAge, Person::setAge);
293+
```
294+
295+
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.
296+
238297
<a name="notes">Notes</a>
239298
-----
240299

0 commit comments

Comments
 (0)