Skip to content

Commit a49401b

Browse files
committed
Adding mapS - mapB on Lens for mapping specific parameters of lenses. Also adding lift and unLift variants for OptionalLens
1 parent c4441f2 commit a49401b

4 files changed

Lines changed: 259 additions & 25 deletions

File tree

src/main/java/com/jnape/palatable/lambda/lens/Lens.java

Lines changed: 69 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,10 @@
126126
* <a href="http://skillsmatter.com/podcast/scala/lenses-compositional-data-access-and-manipulation">about</a>
127127
* <a href="http://r6.ca/blog/20120623T104901Z.html">lenses</a>.
128128
*
129-
* @param <S> The type of the "larger" value for reading
130-
* @param <T> The type of the "larger" value for putting
131-
* @param <A> The type of the "smaller" value that is read
132-
* @param <B> The type of the "smaller" update value
129+
* @param <S> the type of the "larger" value for reading
130+
* @param <T> the type of the "larger" value for putting
131+
* @param <A> the type of the "smaller" value that is read
132+
* @param <B> the type of the "smaller" update value
133133
*/
134134
@FunctionalInterface
135135
public interface Lens<S, T, A, B> extends Functor<T> {
@@ -142,8 +142,8 @@ public interface Lens<S, T, A, B> extends Functor<T> {
142142
* Although the Java type system does not allow enforceability, the functor instance FT should be the same as FB,
143143
* only differentiating in their parameters.
144144
*
145-
* @param <FT> The type of the lifted T
146-
* @param <FB> The type of the lifted B
145+
* @param <FT> the type of the lifted T
146+
* @param <FB> the type of the lifted B
147147
* @return the lens, "fixed" to the functor
148148
*/
149149
default <FT extends Functor<T>, FB extends Functor<B>> Fixed<S, T, A, B, FT, FB> fix() {
@@ -152,7 +152,51 @@ default <FT extends Functor<T>, FB extends Functor<B>> Fixed<S, T, A, B, FT, FB>
152152

153153
@Override
154154
default <U> Lens<S, U, A, B> fmap(Function<? super T, ? extends U> fn) {
155-
return this.compose(Lens.<S, U, S, T>lens(id(), (s, t) -> fn.apply(t)));
155+
return compose(Lens.<S, U, S, T>lens(id(), (s, t) -> fn.apply(t)));
156+
}
157+
158+
/**
159+
* Contravariantly map <code>S</code> to <code>R</code>, yielding a new lens.
160+
*
161+
* @param fn the mapping function
162+
* @param <R> the type of the new "larger" value for reading
163+
* @return the new lens
164+
*/
165+
default <R> Lens<R, T, A, B> mapS(Function<? super R, ? extends S> fn) {
166+
return compose(lens(fn, (r, t) -> t));
167+
}
168+
169+
/**
170+
* Covariantly map <code>T</code> to <code>U</code>, yielding a new lens.
171+
*
172+
* @param fn the mapping function
173+
* @param <U> the type of the new "larger" value for putting
174+
* @return the new lens
175+
*/
176+
default <U> Lens<S, U, A, B> mapT(Function<? super T, ? extends U> fn) {
177+
return fmap(fn);
178+
}
179+
180+
/**
181+
* Covariantly map <code>A</code> to <code>C</code>, yielding a new lens.
182+
*
183+
* @param fn the mapping function
184+
* @param <C> the type of the new "smaller" value that is read
185+
* @return the new lens
186+
*/
187+
default <C> Lens<S, T, C, B> mapA(Function<? super A, ? extends C> fn) {
188+
return andThen(lens(fn, (a, b) -> b));
189+
}
190+
191+
/**
192+
* Contravariantly map <code>B</code> to <code>Z</code>, yielding a new lens.
193+
*
194+
* @param fn the mapping function
195+
* @param <Z> the type of the new "smaller" update value
196+
* @return the new lens
197+
*/
198+
default <Z> Lens<S, T, A, Z> mapB(Function<? super Z, ? extends B> fn) {
199+
return andThen(lens(id(), (a, z) -> fn.apply(z)));
156200
}
157201

158202
/**
@@ -184,10 +228,10 @@ default <Q, R> Lens<Q, R, A, B> compose(Lens<Q, R, S, T> g) {
184228
*
185229
* @param getter the getter function
186230
* @param setter the setter function
187-
* @param <S> The type of the "larger" value for reading
188-
* @param <T> The type of the "larger" value for putting
189-
* @param <A> The type of the "smaller" value that is read
190-
* @param <B> The type of the "smaller" update value
231+
* @param <S> the type of the "larger" value for reading
232+
* @param <T> the type of the "larger" value for putting
233+
* @param <A> the type of the "smaller" value that is read
234+
* @param <B> the type of the "smaller" update value
191235
* @return the lens
192236
*/
193237
static <S, T, A, B> Lens<S, T, A, B> lens(Function<? super S, ? extends A> getter,
@@ -207,8 +251,8 @@ public <FT extends Functor<T>, FB extends Functor<B>> FT apply(Function<? super
207251
*
208252
* @param getter the getter function
209253
* @param setter the setter function
210-
* @param <S> The type of both "larger" values
211-
* @param <A> The type of both "smaller" values
254+
* @param <S> the type of both "larger" values
255+
* @param <A> the type of both "smaller" values
212256
* @return the lens
213257
*/
214258
@SuppressWarnings("unchecked")
@@ -221,8 +265,8 @@ static <S, A> Lens.Simple<S, A> simpleLens(Function<? super S, ? extends A> gett
221265
* A convenience type with a simplified type signature for common lenses with both unified "larger" values and
222266
* unified "smaller" values.
223267
*
224-
* @param <S> The type of both "larger" values
225-
* @param <A> The type of both "smaller" values
268+
* @param <S> the type of both "larger" values
269+
* @param <A> the type of both "smaller" values
226270
*/
227271
@FunctionalInterface
228272
interface Simple<S, A> extends Lens<S, S, A, A> {
@@ -244,10 +288,10 @@ default <B> Lens.Simple<S, B> andThen(Lens.Simple<A, B> f) {
244288
/**
245289
* A convenience type with a simplified type signature for fixed simple lenses.
246290
*
247-
* @param <S> The type of both "larger" values
248-
* @param <A> The type of both "smaller" values
249-
* @param <FS> The type of the lifted s
250-
* @param <FA> The type of the lifted A
291+
* @param <S> the type of both "larger" values
292+
* @param <A> the type of both "smaller" values
293+
* @param <FS> the type of the lifted s
294+
* @param <FA> the type of the lifted A
251295
*/
252296
@FunctionalInterface
253297
interface Fixed<S, A, FS extends Functor<S>, FA extends Functor<A>>
@@ -259,12 +303,12 @@ interface Fixed<S, A, FS extends Functor<S>, FA extends Functor<A>>
259303
* A lens that has been fixed to a functor. Because the lens is no longer polymorphic, it can additionally be safely
260304
* represented as an Fn2.
261305
*
262-
* @param <S> The type of the "larger" value for reading
263-
* @param <T> The type of the "larger" value for putting
264-
* @param <A> The type of the "smaller" value that is read
265-
* @param <B> The type of the "smaller" update value
266-
* @param <FT> The type of the lifted T
267-
* @param <FB> The type of the lifted B
306+
* @param <S> the type of the "larger" value for reading
307+
* @param <T> the type of the "larger" value for putting
308+
* @param <A> the type of the "smaller" value that is read
309+
* @param <B> the type of the "smaller" update value
310+
* @param <FT> the type of the lifted T
311+
* @param <FB> the type of the lifted B
268312
*/
269313
@FunctionalInterface
270314
interface Fixed<S, T, A, B, FT extends Functor<T>, FB extends Functor<B>>

src/main/java/com/jnape/palatable/lambda/lens/lenses/OptionalLens.java

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,122 @@ private OptionalLens() {
2323
public static <V> Lens.Simple<V, Optional<V>> asOptional() {
2424
return simpleLens(Optional::ofNullable, (v, optV) -> optV.orElse(v));
2525
}
26+
27+
/**
28+
* Given a lens and a default <code>S</code>, lift <code>S</code> into Optional.
29+
*
30+
* @param lens the lens
31+
* @param defaultS the S to use if an empty Optional value is given
32+
* @param <S> the type of the "larger" value for reading
33+
* @param <T> the type of the "larger" value for putting
34+
* @param <A> the type of the "smaller" value that is read
35+
* @param <B> the type of the "smaller" update value
36+
* @return the lens with S lifted
37+
*/
38+
public static <S, T, A, B> Lens<Optional<S>, T, A, B> liftS(Lens<S, T, A, B> lens, S defaultS) {
39+
return lens.mapS(optS -> optS.orElse(defaultS));
40+
}
41+
42+
/**
43+
* Given a lens, lift <code>T</code> into Optional.
44+
*
45+
* @param lens the lens
46+
* @param <S> the type of the "larger" value for reading
47+
* @param <T> the type of the "larger" value for putting
48+
* @param <A> the type of the "smaller" value that is read
49+
* @param <B> the type of the "smaller" update value
50+
* @return the lens with T lifted
51+
*/
52+
public static <S, T, A, B> Lens<S, Optional<T>, A, B> liftT(Lens<S, T, A, B> lens) {
53+
return lens.mapT(Optional::ofNullable);
54+
}
55+
56+
/**
57+
* Given a lens, lift <code>A</code> into Optional.
58+
*
59+
* @param lens the lens
60+
* @param <S> the type of the "larger" value for reading
61+
* @param <T> the type of the "larger" value for putting
62+
* @param <A> the type of the "smaller" value that is read
63+
* @param <B> the type of the "smaller" update value
64+
* @return the lens with A lifted
65+
*/
66+
public static <S, T, A, B> Lens<S, T, Optional<A>, B> liftA(Lens<S, T, A, B> lens) {
67+
return lens.mapA(Optional::ofNullable);
68+
}
69+
70+
/**
71+
* Given a lens and a default <code>B</code>, lift <code>B</code> into Optional.
72+
*
73+
* @param lens the lens
74+
* @param defaultB the B to use if an empty Optional value is given
75+
* @param <S> the type of the "larger" value for reading
76+
* @param <T> the type of the "larger" value for putting
77+
* @param <A> the type of the "smaller" value that is read
78+
* @param <B> the type of the "smaller" update value
79+
* @return the lens with B lifted
80+
*/
81+
public static <S, T, A, B> Lens<S, T, A, Optional<B>> liftB(Lens<S, T, A, B> lens, B defaultB) {
82+
return lens.mapB(optB -> optB.orElse(defaultB));
83+
}
84+
85+
/**
86+
* Given a lens with <code>S</code> lifted into Optional, flatten <code>S</code> back down.
87+
*
88+
* @param lens the lens
89+
* @param <S> the type of the "larger" value for reading
90+
* @param <T> the type of the "larger" value for putting
91+
* @param <A> the type of the "smaller" value that is read
92+
* @param <B> the type of the "smaller" update value
93+
* @return the lens with S flattened
94+
*/
95+
public static <S, T, A, B> Lens<S, T, A, B> unLiftS(Lens<Optional<S>, T, A, B> lens) {
96+
return lens.mapS(Optional::ofNullable);
97+
}
98+
99+
/**
100+
* Given a lens with <code>T</code> lifted into Optional and a default <code>T</code>, flatten <code>T</code> back
101+
* down.
102+
*
103+
* @param lens the lens
104+
* @param defaultT the T to use if lens produces an empty Optional
105+
* @param <S> the type of the "larger" value for reading
106+
* @param <T> the type of the "larger" value for putting
107+
* @param <A> the type of the "smaller" value that is read
108+
* @param <B> the type of the "smaller" update value
109+
* @return the lens with T flattened
110+
*/
111+
public static <S, T, A, B> Lens<S, T, A, B> unLiftT(Lens<S, Optional<T>, A, B> lens, T defaultT) {
112+
return lens.mapT(optT -> optT.orElse(defaultT));
113+
}
114+
115+
/**
116+
* Given a lens with <code>A</code> lifted into Optional and a default <code>A</code>, flatten <code>A</code> back
117+
* down.
118+
*
119+
* @param lens the lens
120+
* @param defaultA the A to use if lens produces an empty Optional
121+
* @param <S> the type of the "larger" value for reading
122+
* @param <T> the type of the "larger" value for putting
123+
* @param <A> the type of the "smaller" value that is read
124+
* @param <B> the type of the "smaller" update value
125+
* @return the lens with A flattened
126+
*/
127+
public static <S, T, A, B> Lens<S, T, A, B> unLiftA(Lens<S, T, Optional<A>, B> lens, A defaultA) {
128+
return lens.mapA(optA -> optA.orElse(defaultA));
129+
}
130+
131+
/**
132+
* Given a lens with <code>B</code> lifted, flatten <code>B</code> back down.
133+
*
134+
* @param lens the lens
135+
* @param <S> the type of the "larger" value for reading
136+
* @param <T> the type of the "larger" value for putting
137+
* @param <A> the type of the "smaller" value that is read
138+
* @param <B> the type of the "smaller" update value
139+
* @return the lens with B flattened
140+
*/
141+
public static <S, T, A, B> Lens<S, T, A, B> unLiftB(Lens<S, T, A, Optional<B>> lens) {
142+
return lens.mapB(Optional::ofNullable);
143+
}
26144
}

src/test/java/com/jnape/palatable/lambda/lens/LensTest.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@
77

88
import java.util.List;
99
import java.util.Map;
10+
import java.util.Optional;
1011
import java.util.Set;
1112

1213
import static com.jnape.palatable.lambda.lens.Lens.lens;
1314
import static com.jnape.palatable.lambda.lens.functions.Set.set;
1415
import static com.jnape.palatable.lambda.lens.functions.View.view;
16+
import static java.lang.Integer.parseInt;
1517
import static java.util.Arrays.asList;
1618
import static java.util.Collections.singleton;
1719
import static java.util.Collections.singletonList;
@@ -51,6 +53,19 @@ public void functorProperties() {
5153
assertEquals(false, set(LENS.fmap(Set::isEmpty), 1, singletonList("foo")));
5254
}
5355

56+
@Test
57+
public void mapsIndividuallyOverParameters() {
58+
Lens<String, Boolean, Character, Integer> lens = lens(s -> s.charAt(0), (s, b) -> s.length() == b);
59+
Lens<Optional<String>, Optional<Boolean>, Optional<Character>, Optional<Integer>> theGambit = lens
60+
.mapS((Optional<String> optS) -> optS.orElse(""))
61+
.mapT(Optional::ofNullable)
62+
.mapA(Optional::ofNullable)
63+
.mapB((Optional<Integer> optI) -> optI.orElse(-1));
64+
65+
Lens.Fixed<Optional<String>, Optional<Boolean>, Optional<Character>, Optional<Integer>, Identity<Optional<Boolean>>, Identity<Optional<Integer>>> fixed = theGambit.fix();
66+
assertEquals(Optional.of(true), fixed.apply(optC -> new Identity<>(optC.map(c -> parseInt(Character.toString(c)))), Optional.of("321")).runIdentity());
67+
}
68+
5469
@Test
5570
public void composition() {
5671
Map<String, List<String>> map = singletonMap("foo", asList("one", "two", "three"));

src/test/java/com/jnape/palatable/lambda/lens/lenses/OptionalLensTest.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,29 @@
11
package com.jnape.palatable.lambda.lens.lenses;
22

33
import com.jnape.palatable.lambda.lens.Lens;
4+
import org.junit.Before;
45
import org.junit.Test;
56

67
import java.util.Optional;
78

9+
import static com.jnape.palatable.lambda.lens.Lens.lens;
810
import static com.jnape.palatable.lambda.lens.functions.Set.set;
911
import static com.jnape.palatable.lambda.lens.functions.View.view;
12+
import static com.jnape.palatable.lambda.lens.lenses.OptionalLens.liftA;
13+
import static com.jnape.palatable.lambda.lens.lenses.OptionalLens.liftB;
14+
import static com.jnape.palatable.lambda.lens.lenses.OptionalLens.liftS;
15+
import static com.jnape.palatable.lambda.lens.lenses.OptionalLens.liftT;
1016
import static org.junit.Assert.assertEquals;
1117

1218
public class OptionalLensTest {
1319

20+
private Lens<String, Boolean, Character, Integer> lens;
21+
22+
@Before
23+
public void setUp() throws Exception {
24+
lens = lens(s -> s.charAt(0), (s, b) -> s.length() == b);
25+
}
26+
1427
@Test
1528
public void asOptionalWrapsValuesInOptional() {
1629
Lens.Simple<String, Optional<String>> asOptional = OptionalLens.asOptional();
@@ -20,4 +33,48 @@ public void asOptionalWrapsValuesInOptional() {
2033
assertEquals("bar", set(asOptional, Optional.of("bar"), "foo"));
2134
assertEquals("foo", set(asOptional, Optional.empty(), "foo"));
2235
}
36+
37+
@Test
38+
public void liftSLiftsSToOptional() {
39+
assertEquals((Character) '3', view(liftS(lens, "3"), Optional.empty()));
40+
}
41+
42+
@Test
43+
public void liftTLiftsTToOptional() {
44+
assertEquals(Optional.of(true), set(liftT(lens), 3, "123"));
45+
}
46+
47+
@Test
48+
public void liftALiftsAToOptional() {
49+
assertEquals(Optional.of('1'), view(liftA(lens), "123"));
50+
}
51+
52+
@Test
53+
public void liftBLiftsBToOptional() {
54+
assertEquals(true, set(OptionalLens.liftB(lens, 1), Optional.empty(), "1"));
55+
}
56+
57+
@Test
58+
public void unLiftSPullsSOutOfOptional() {
59+
Lens<Optional<String>, Optional<Boolean>, Optional<Character>, Optional<Integer>> liftedToOptional = liftS(liftT(liftA(liftB(lens, 3))), "123");
60+
assertEquals(Optional.of('f'), view(OptionalLens.unLiftS(liftedToOptional), "f"));
61+
}
62+
63+
@Test
64+
public void unLiftTPullsTOutOfOptional() {
65+
Lens<Optional<String>, Optional<Boolean>, Optional<Character>, Optional<Integer>> liftedToOptional = liftS(liftT(liftA(liftB(lens, 3))), "123");
66+
assertEquals(true, set(OptionalLens.unLiftT(liftedToOptional, false), Optional.of(3), Optional.of("321")));
67+
}
68+
69+
@Test
70+
public void unLiftAPullsAOutOfOptional() {
71+
Lens<Optional<String>, Optional<Boolean>, Optional<Character>, Optional<Integer>> liftedToOptional = liftS(liftT(liftA(liftB(lens, 3))), "123");
72+
assertEquals((Character) '1', view(OptionalLens.unLiftA(liftedToOptional, '4'), Optional.empty()));
73+
}
74+
75+
@Test
76+
public void unLiftBPullsBOutOfOptional() {
77+
Lens<Optional<String>, Optional<Boolean>, Optional<Character>, Optional<Integer>> liftedToOptional = liftS(liftT(liftA(liftB(lens, 3))), "123");
78+
assertEquals(Optional.of(true), set(OptionalLens.unLiftB(liftedToOptional), 3, Optional.of("321")));
79+
}
2380
}

0 commit comments

Comments
 (0)