Skip to content

Commit c72ef91

Browse files
committed
Lens#both for dually focusing with two lenses at once
1 parent 6a1ca16 commit c72ef91

File tree

3 files changed

+56
-0
lines changed

3 files changed

+56
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
2323
- `Span`, for splitting an `Iterable` into contiguous elements matching a predicate
2424
- `MagnetizeBy` and `Magnetize`, for grouping elements by pairwise predicate tests
2525
- `Both`, for dually applying two functions and producing a `Tuple2` of their results
26+
- `Lens#both`, for dually focusing with two lenses at once
2627

2728
### Deprecated
2829
- `Either#trying` in favor of `Try#trying`

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package com.jnape.palatable.lambda.lens;
22

3+
import com.jnape.palatable.lambda.adt.hlist.Tuple2;
4+
import com.jnape.palatable.lambda.functions.Fn1;
35
import com.jnape.palatable.lambda.functions.Fn2;
6+
import com.jnape.palatable.lambda.functions.builtin.fn2.Both;
47
import com.jnape.palatable.lambda.functor.Applicative;
58
import com.jnape.palatable.lambda.functor.Functor;
69
import com.jnape.palatable.lambda.functor.Profunctor;
@@ -311,6 +314,23 @@ static <S, A> Lens.Simple<S, A> simpleLens(Function<? super S, ? extends A> gett
311314
return adapt(lens(getter, setter));
312315
}
313316

317+
/**
318+
* Dually focus on two lenses at the same time. Requires <code>S</code> and <code>T</code> to be invariant between
319+
* lenses.
320+
*
321+
* @param f the first lens
322+
* @param g the second lens
323+
* @param <S> both larger values
324+
* @param <A> f's smaller viewing value
325+
* @param <B> g's smaller viewing value
326+
* @param <C> f's smaller setting value
327+
* @param <D> g's smaller setting value
328+
* @return the dual-focus lens
329+
*/
330+
static <S, A, B, C, D> Lens<S, S, Tuple2<A, B>, Tuple2<C, D>> both(Lens<S, S, A, C> f, Lens<S, S, B, D> g) {
331+
return lens(Both.both(view(f), view(g)), (s, cd) -> cd.biMap(set(f), set(g)).into(Fn1::compose).apply(s));
332+
}
333+
314334
/**
315335
* A convenience type with a simplified type signature for common lenses with both unified "larger" values and
316336
* unified "smaller" values.
@@ -335,11 +355,32 @@ default <B> Lens.Simple<S, B> andThen(Lens.Simple<A, B> f) {
335355
return f.compose(this);
336356
}
337357

358+
/**
359+
* Adapt a {@link Lens} with the right variance to a {@link Lens.Simple}.
360+
*
361+
* @param lens the lens
362+
* @param <S> S/T
363+
* @param <A> A/B
364+
* @return the simple lens
365+
*/
338366
@SuppressWarnings("unchecked")
339367
static <S, A> Simple<S, A> adapt(Lens<S, S, A, A> lens) {
340368
return lens::apply;
341369
}
342370

371+
/**
372+
* Specialization of {@link Lens#both(Lens, Lens)} for simple lenses.
373+
*
374+
* @param f the first lens
375+
* @param g the second lens
376+
* @param <S> both lens larger values
377+
* @param <A> both lens smaller values
378+
* @return the dual-focus simple lens
379+
*/
380+
static <S, A> Lens.Simple<S, Tuple2<A, A>> both(Lens<S, S, A, A> f, Lens<S, S, A, A> g) {
381+
return adapt(Lens.both(f, g));
382+
}
383+
343384
/**
344385
* A convenience type with a simplified type signature for fixed simple lenses.
345386
*

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.jnape.palatable.lambda.lens;
22

33
import com.jnape.palatable.lambda.adt.Maybe;
4+
import com.jnape.palatable.lambda.adt.hlist.Tuple2;
45
import com.jnape.palatable.lambda.functions.Fn1;
56
import com.jnape.palatable.lambda.functor.builtin.Const;
67
import com.jnape.palatable.lambda.functor.builtin.Identity;
@@ -18,7 +19,10 @@
1819
import java.util.Set;
1920

2021
import static com.jnape.palatable.lambda.adt.Maybe.just;
22+
import static com.jnape.palatable.lambda.adt.hlist.HList.tuple;
23+
import static com.jnape.palatable.lambda.lens.Lens.both;
2124
import static com.jnape.palatable.lambda.lens.Lens.lens;
25+
import static com.jnape.palatable.lambda.lens.Lens.simpleLens;
2226
import static com.jnape.palatable.lambda.lens.functions.Set.set;
2327
import static com.jnape.palatable.lambda.lens.functions.View.view;
2428
import static java.lang.Integer.parseInt;
@@ -89,4 +93,14 @@ public void andThenComposesInReverse() {
8993
assertEquals("one", view(EARLIER_LENS.andThen(LENS), map));
9094
assertEquals(singletonMap("foo", singleton(1)), set(EARLIER_LENS.andThen(LENS), 1, map));
9195
}
96+
97+
@Test
98+
public void bothSplitsFocusBetweenLenses() {
99+
Lens<String, String, Character, Character> firstChar = simpleLens(s -> s.charAt(0), (s, c) -> c + s.substring(1));
100+
Lens<String, String, Integer, Integer> length = simpleLens(String::length, (s, k) -> s.substring(0, k));
101+
Lens<String, String, Tuple2<Character, Integer>, Tuple2<Character, Integer>> both = both(firstChar, length);
102+
103+
assertEquals(tuple('a', 3), view(both, "abc"));
104+
assertEquals("zb", set(both, tuple('z', 2), "abc"));
105+
}
92106
}

0 commit comments

Comments
 (0)