Skip to content

Commit cbac3f5

Browse files
committed
Finally adding ISO, isomorphisms between types
1 parent f0009b8 commit cbac3f5

15 files changed

Lines changed: 713 additions & 166 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
1414
- `HListLens#tail` is now covariant in `Tail` parameter
1515
- More functions now automatically deforest nested calls (`concat` `cons`, `cycle`, `distinct`, `drop`, `dropwhile`, `filter`, `map`, `reverse`, `snoc`, `take`, `takewhile`, `tail`)
1616
- `Flatten` calls `Iterator#hasNext` less aggressively, allowing for better laziness
17+
- `Lens` subtypes `LensLike`
18+
- `View`/`Set`/`Over` now only require `LensLike`
1719

1820
### Added
1921
- `BoundedBifunctor`, a `Bifunctor` super type that offers upper bounds for both parameters
@@ -28,6 +30,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
2830
- `Lens#both`, for dually focusing with two lenses at once
2931
- `IfThenElse`, an expression form for `if` statements
3032
- `CheckedRunnable` and `CheckedSupplier` conversion and convenience methods
33+
- `LensLike`, common capabilities that make a type usable as if it were a `Lens`
34+
- `Iso`, isomorphisms between two types (invertible functions that are also lenses)
35+
- `Exchange`, a `Profunctor` that can extract the morphisms from an `Iso`
36+
- `HMapLens`, lenses focusing on `HMap`
37+
- `MapLens#mappingValues(Iso)`, a lawful lens that maps the values of a `j.u.Map`
38+
39+
### Deprecated
40+
- `MapLens#mappingValues(Function)` is now deprecated in favor of the overload that takes an <code>Iso</code>
3141

3242
## [2.1.1] - 2018-01-16
3343
### Changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package com.jnape.palatable.lambda.functor.builtin;
2+
3+
import com.jnape.palatable.lambda.functor.Profunctor;
4+
import com.jnape.palatable.lambda.lens.Iso;
5+
6+
import java.util.function.Function;
7+
8+
/**
9+
* A profunctor used to extract the isomorphic functions an {@link Iso} is composed of.
10+
*
11+
* @param <A> the smaller viewed value of an {@link Iso}
12+
* @param <B> the smaller viewing value of an {@link Iso}
13+
* @param <S> the larger viewing value of an {@link Iso}
14+
* @param <T> the larger viewed value of an {@link Iso}
15+
*/
16+
public final class Exchange<A, B, S, T> implements Profunctor<S, T, Exchange<A, B, ?, ?>> {
17+
private final Function<? super S, ? extends A> sa;
18+
private final Function<? super B, ? extends T> bt;
19+
20+
public Exchange(Function<? super S, ? extends A> sa, Function<? super B, ? extends T> bt) {
21+
this.sa = sa;
22+
this.bt = bt;
23+
}
24+
25+
public Function<? super S, ? extends A> sa() {
26+
return sa;
27+
}
28+
29+
public Function<? super B, ? extends T> bt() {
30+
return bt;
31+
}
32+
33+
@Override
34+
public <Z, C> Exchange<A, B, Z, C> diMap(Function<? super Z, ? extends S> lFn,
35+
Function<? super T, ? extends C> rFn) {
36+
return new Exchange<>(lFn.andThen(sa), bt.andThen(rFn));
37+
}
38+
39+
@Override
40+
@SuppressWarnings("unchecked")
41+
public <Z> Exchange<A, B, Z, T> diMapL(Function<? super Z, ? extends S> fn) {
42+
return (Exchange<A, B, Z, T>) Profunctor.super.diMapL(fn);
43+
}
44+
45+
@Override
46+
@SuppressWarnings("unchecked")
47+
public <C> Exchange<A, B, S, C> diMapR(Function<? super T, ? extends C> fn) {
48+
return (Exchange<A, B, S, C>) Profunctor.super.diMapR(fn);
49+
}
50+
51+
@Override
52+
@SuppressWarnings("unchecked")
53+
public <Z> Exchange<A, B, Z, T> contraMap(Function<? super Z, ? extends S> fn) {
54+
return (Exchange<A, B, Z, T>) Profunctor.super.contraMap(fn);
55+
}
56+
}
Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
package com.jnape.palatable.lambda.lens;
2+
3+
import com.jnape.palatable.lambda.adt.hlist.Tuple2;
4+
import com.jnape.palatable.lambda.functions.Fn1;
5+
import com.jnape.palatable.lambda.functions.Fn2;
6+
import com.jnape.palatable.lambda.functor.Applicative;
7+
import com.jnape.palatable.lambda.functor.Functor;
8+
import com.jnape.palatable.lambda.functor.Profunctor;
9+
import com.jnape.palatable.lambda.functor.builtin.Exchange;
10+
import com.jnape.palatable.lambda.functor.builtin.Identity;
11+
import com.jnape.palatable.lambda.lens.functions.Over;
12+
import com.jnape.palatable.lambda.lens.functions.Set;
13+
import com.jnape.palatable.lambda.lens.functions.View;
14+
import com.jnape.palatable.lambda.monad.Monad;
15+
16+
import java.util.function.Function;
17+
18+
import static com.jnape.palatable.lambda.functions.Fn1.fn1;
19+
import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly;
20+
import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id;
21+
import static com.jnape.palatable.lambda.lens.Iso.Simple.adapt;
22+
import static com.jnape.palatable.lambda.lens.functions.View.view;
23+
24+
/**
25+
* An {@link Iso} (short for "isomorphism") is an invertible {@link Lens}: a {@link LensLike} encoding of a
26+
* bi-directional focusing of two types, and like <code>{@link Lens}es</code>, can be <code>{@link View}ed</code>,
27+
* {@link Set}, and <code>{@link Over}ed</code>.
28+
* <p>
29+
* As an example, consider the isomorphism between valid {@link String}s and {@link Integer}s:
30+
* <pre>
31+
* {@code
32+
* Iso<String, String, Integer, Integer> stringIntIso = Iso.iso(Integer::parseInt, Object::toString);
33+
* Integer asInt = view(stringIntIso, "123"); // 123
34+
* String asString = view(stringIntIso.mirror(), 123); // "123"
35+
* }
36+
* </pre>
37+
* In the previous example, <code>stringIntIso</code> can be viewed as a <code>{@link Lens}&lt;String, String, Integer,
38+
* Integer&gt;</code>, and can be <code>{@link Iso#mirror}ed</code> and viewed as a <code>{@link Lens}&lt;Integer,
39+
* Integer, String, String&gt;</code>.
40+
* <p>
41+
* As with {@link Lens}, variance is supported between <code>S/T</code> and <code>A/B</code>, and where these pairs do
42+
* not vary, a {@link Simple} iso can be used (for instance, in the previous example, <code>stringIntIso</code> could
43+
* have had the simplified <code>Iso.Simple&lt;String, Integer&gt;</code> type).
44+
* <p>
45+
* For more information, read about <a href="https://hackage.haskell.org/package/lens-4.16.1/docs/Control-Lens-Iso.html">isos</a>.
46+
*
47+
* @param <S> the larger type for focusing
48+
* @param <T> the larger type for mirrored focusing
49+
* @param <A> the smaller type for focusing
50+
* @param <B> the smaller type for mirrored focusing
51+
*/
52+
@FunctionalInterface
53+
public interface Iso<S, T, A, B> extends LensLike<S, T, A, B, Iso> {
54+
55+
<P extends Profunctor, F extends Functor, FB extends Functor<B, F>, FT extends Functor<T, F>,
56+
PAFB extends Profunctor<A, FB, P>,
57+
PSFT extends Profunctor<S, FT, P>> PSFT apply(PAFB pafb);
58+
59+
@Override
60+
default <F extends Functor, FT extends Functor<T, F>, FB extends Functor<B, F>> FT apply(
61+
Function<? super A, ? extends FB> fn, S s) {
62+
return this.<Fn1, F, FB, FT, Fn1<A, FB>, Fn1<S, FT>>apply(fn1(fn)).apply(s);
63+
}
64+
65+
/**
66+
* Convert this {@link Iso} into a {@link Lens}.
67+
*
68+
* @return the equivalent lens
69+
*/
70+
default Lens<S, T, A, B> toLens() {
71+
return new Lens<S, T, A, B>() {
72+
@Override
73+
public <F extends Functor, FT extends Functor<T, F>, FB extends Functor<B, F>> FT apply(
74+
Function<? super A, ? extends FB> fn, S s) {
75+
return Iso.this.apply(fn1(fn), s);
76+
}
77+
};
78+
}
79+
80+
/**
81+
* Flip this {@link Iso} around.
82+
*
83+
* @return the mirrored {@link Iso}
84+
*/
85+
default Iso<B, A, T, S> mirror() {
86+
return unIso().into((sa, bt) -> iso(bt, sa));
87+
}
88+
89+
/**
90+
* Destructure this {@link Iso} into the two functions <code>S -&lt; A</code> and <code>B -&lt; T</code> that
91+
* constitute the isomorphism.
92+
*
93+
* @return the destructured iso
94+
*/
95+
default Tuple2<? extends Function<? super S, ? extends A>, ? extends Function<? super B, ? extends T>> unIso() {
96+
return Tuple2.fill(this.<Exchange<A, B, ?, ?>, Identity, Identity<B>, Identity<T>,
97+
Exchange<A, B, A, Identity<B>>,
98+
Exchange<A, B, S, Identity<T>>>apply(new Exchange<>(id(), Identity::new)).diMapR(Identity::runIdentity))
99+
.biMap(Exchange::sa, Exchange::bt);
100+
}
101+
102+
@Override
103+
default <U> Iso<S, U, A, B> fmap(Function<? super T, ? extends U> fn) {
104+
return LensLike.super.<U>fmap(fn).coerce();
105+
}
106+
107+
@Override
108+
default <U> Iso<S, U, A, B> pure(U u) {
109+
return iso(view(this), constantly(u));
110+
}
111+
112+
@Override
113+
default <U> Iso<S, U, A, B> zip(Applicative<Function<? super T, ? extends U>, LensLike<S, ?, A, B, Iso>> appFn) {
114+
return LensLike.super.zip(appFn).coerce();
115+
}
116+
117+
@Override
118+
default <U> Iso<S, U, A, B> discardL(Applicative<U, LensLike<S, ?, A, B, Iso>> appB) {
119+
return LensLike.super.discardL(appB).coerce();
120+
}
121+
122+
@Override
123+
default <U> Iso<S, T, A, B> discardR(Applicative<U, LensLike<S, ?, A, B, Iso>> appB) {
124+
return LensLike.super.discardR(appB).coerce();
125+
}
126+
127+
@Override
128+
default <U> Iso<S, U, A, B> flatMap(Function<? super T, ? extends Monad<U, LensLike<S, ?, A, B, Iso>>> fn) {
129+
return unIso().fmap(bt -> Fn2.<B, B, U>fn2(fn1(bt.andThen(fn.<Iso<S, U, A, B>>andThen(Applicative::coerce))
130+
.andThen(Iso::unIso)
131+
.andThen(Tuple2::_2)
132+
.andThen(Fn1::fn1))))
133+
.fmap(Fn2::uncurry)
134+
.fmap(bbu -> bbu.<B>diMapL(Tuple2::fill))
135+
.into(Iso::iso);
136+
}
137+
138+
@Override
139+
default <R> Iso<R, T, A, B> diMapL(Function<? super R, ? extends S> fn) {
140+
return LensLike.super.<R>diMapL(fn).coerce();
141+
}
142+
143+
@Override
144+
default <U> Iso<S, U, A, B> diMapR(Function<? super T, ? extends U> fn) {
145+
return LensLike.super.<U>diMapR(fn).coerce();
146+
}
147+
148+
@Override
149+
default <R, U> Iso<R, U, A, B> diMap(Function<? super R, ? extends S> lFn,
150+
Function<? super T, ? extends U> rFn) {
151+
return LensLike.super.<R, U>diMap(lFn, rFn).coerce();
152+
}
153+
154+
@Override
155+
default <R> Iso<R, T, A, B> contraMap(Function<? super R, ? extends S> fn) {
156+
return LensLike.super.<R>contraMap(fn).coerce();
157+
}
158+
159+
@Override
160+
default <R> Iso<R, T, A, B> mapS(Function<? super R, ? extends S> fn) {
161+
return unIso().biMapL(f -> f.compose(fn)).into(Iso::iso);
162+
}
163+
164+
@Override
165+
default <U> Iso<S, U, A, B> mapT(Function<? super T, ? extends U> fn) {
166+
return unIso().biMapR(f -> f.andThen(fn)).into(Iso::iso);
167+
}
168+
169+
@Override
170+
default <C> Iso<S, T, C, B> mapA(Function<? super A, ? extends C> fn) {
171+
return unIso().biMapL(f -> f.andThen(fn)).into(Iso::iso);
172+
}
173+
174+
@Override
175+
default <Z> Iso<S, T, A, Z> mapB(Function<? super Z, ? extends B> fn) {
176+
return unIso().biMapR(f -> f.compose(fn)).into(Iso::iso);
177+
}
178+
179+
/**
180+
* Left-to-right composition of {@link Iso}.
181+
*
182+
* @param f the iso to apply after this one
183+
* @param <C> the smaller type the first larger type can be viewed as
184+
* @param <D> the smaller type that can be viewed as the second larger type
185+
* @return the composed {@link Iso}
186+
*/
187+
default <C, D> Iso<S, T, C, D> andThen(Iso<A, B, C, D> f) {
188+
return unIso().into((sa, bt) -> f.unIso().into((ac, db) -> iso(sa.andThen(ac), db.andThen(bt))));
189+
}
190+
191+
/**
192+
* Right-to-left composition of {@link Iso}.
193+
*
194+
* @param g the iso to apply before this one
195+
* @param <Q> the larger type that can be viewed as the first smaller type
196+
* @param <R> the larger type the second smaller type can be viewed as
197+
* @return the composed {@link Iso}
198+
*/
199+
default <Q, R> Iso<Q, R, A, B> compose(Iso<Q, R, S, T> g) {
200+
return g.andThen(this);
201+
}
202+
203+
/**
204+
* Static factory method for creating an iso from a function and it's inverse.
205+
*
206+
* @param f the function
207+
* @param g f's inverse
208+
* @param <S> the larger type for focusing
209+
* @param <T> the larger type for mirrored focusing
210+
* @param <A> the smaller type for focusing
211+
* @param <B> the smaller type for mirrored focusing
212+
* @return the iso
213+
*/
214+
static <S, T, A, B> Iso<S, T, A, B> iso(Function<? super S, ? extends A> f,
215+
Function<? super B, ? extends T> g) {
216+
return new Iso<S, T, A, B>() {
217+
@Override
218+
@SuppressWarnings("unchecked")
219+
public <P extends Profunctor, F extends Functor, FB extends Functor<B, F>, FT extends Functor<T, F>, PAFB extends Profunctor<A, FB, P>, PSFT extends Profunctor<S, FT, P>> PSFT apply(
220+
PAFB pafb) {
221+
return (PSFT) pafb.<S, FT>diMap(f, fb -> (FT) fb.<T>fmap(g));
222+
}
223+
};
224+
}
225+
226+
/**
227+
* Static factory method for creating a simple {@link Iso} from a function and its inverse.
228+
*
229+
* @param f a function
230+
* @param g f's inverse
231+
* @param <S> one side of the isomorphism
232+
* @param <A> the other side of the isomorphism
233+
* @return the simple iso
234+
*/
235+
static <S, A> Iso.Simple<S, A> simpleIso(Function<? super S, ? extends A> f, Function<? super A, ? extends S> g) {
236+
return adapt(iso(f, g));
237+
}
238+
239+
/**
240+
* A convenience type with a simplified type signature for common isos with both unified "larger" values and
241+
* unified "smaller" values.
242+
*
243+
* @param <S> the type of both "larger" values
244+
* @param <A> the type of both "smaller" values
245+
*/
246+
interface Simple<S, A> extends Iso<S, S, A, A>, LensLike.Simple<S, A, Iso> {
247+
248+
@Override
249+
default Iso.Simple<A, S> mirror() {
250+
return Iso.Simple.adapt(Iso.super.mirror());
251+
}
252+
253+
@Override
254+
default Lens.Simple<S, A> toLens() {
255+
return Lens.Simple.adapt(Iso.super.toLens());
256+
}
257+
258+
/**
259+
* Compose two simple isos from right to left.
260+
*
261+
* @param g the other simple iso
262+
* @param <R> the other simple iso' larger type
263+
* @return the composed simple iso
264+
*/
265+
default <R> Iso.Simple<R, A> compose(Iso.Simple<R, S> g) {
266+
return g.andThen(this);
267+
}
268+
269+
/**
270+
* Compose two simple isos from left to right.
271+
*
272+
* @param f the other simple iso
273+
* @param <B> the other simple iso' smaller type
274+
* @return the composed simple iso
275+
*/
276+
default <B> Iso.Simple<S, B> andThen(Iso.Simple<A, B> f) {
277+
return Iso.Simple.adapt(f.compose(this));
278+
}
279+
280+
/**
281+
* Adapt an {@link Iso} with the right variance to an {@link Iso.Simple}.
282+
*
283+
* @param iso the iso
284+
* @param <S> S/T
285+
* @param <A> A/B
286+
* @return the simple iso
287+
*/
288+
@SuppressWarnings("unchecked")
289+
static <S, A> Iso.Simple<S, A> adapt(Iso<S, S, A, A> iso) {
290+
return iso::apply;
291+
}
292+
}
293+
}

0 commit comments

Comments
 (0)