com.jnape.palatable.lambda.lens.Lens Maven / Gradle / Ivy
Show all versions of lambda Show documentation
package com.jnape.palatable.lambda.lens;
import com.jnape.palatable.lambda.functions.Fn2;
import com.jnape.palatable.lambda.functor.Applicative;
import com.jnape.palatable.lambda.functor.Functor;
import com.jnape.palatable.lambda.functor.Profunctor;
import com.jnape.palatable.lambda.monad.Monad;
import java.util.function.BiFunction;
import java.util.function.Function;
import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id;
import static com.jnape.palatable.lambda.lens.Lens.Simple.adapt;
import static com.jnape.palatable.lambda.lens.functions.Over.over;
import static com.jnape.palatable.lambda.lens.functions.Set.set;
import static com.jnape.palatable.lambda.lens.functions.View.view;
/**
* An approximation of van Laarhoven lenses.
*
* A "lens" can be considered in its simplest form as the conjugation of a "getter" and a "setter"; that is, a
* unification type representing the way to retrieve a "smaller" value A
from a "larger" value
* S
, as well as a way to update a "smaller" value B
of a "larger" value S
,
* producing another "larger" value T
.
*
* Consider the following example:
*
* {@code
* 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);
* }
* }
* }
*
* A lens that focused on the age
field of an instance of Person
might look like this:
*
* {@code
* Lens ageLens = Lens.lens(Person::getAge, Person::setAge);
*
* Person adult = new Person(18);
* Integer age = view(ageLens, adult); // 18
*
* Person olderAdult = set(ageLens, 19, adult);
* Integer olderAge = view(ageLens, olderAdult); // 19
* }
*
* The pattern of a getter and setter that mutually agree on both A
and B
as well as on both
* S
and T
is so common that this can be given a simplified type signature:
*
* {@code
* Lens.Simple ageLens = Lens.simpleLens(Person::getAge, Person::setAge);
*
* Person adult = new Person(18);
* Integer age = view(ageLens, adult); // 18
*
* Person olderAdult = set(ageLens, 19, adult);
* Integer olderAge = view(ageLens, olderAdult); // 19
* }
*
* However, consider if age
could be updated on a Person
by being provided a date of birth, in
* the form of a LocalDate
:
*
* {@code
* 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()));
* }
* }
* }
*
* This is why Lens has both an A
and a B
: A
is the value for "getting", and
* B
is the potentially different value for "setting". This distinction makes lenses powerful enough to
* express the more complicated setAge
case naturally:
*
* {@code
* Lens ageDobLens = Lens.lens(Person::getAge, Person::setAge);
*
* Person adult = new Person(18);
* Integer age = view(ageDobLens, adult); // 18
*
* Person olderAdult = set(ageDobLens, LocalDate.of(1997, 1, 1), adult);
* Integer olderAge = view(ageDobLens, olderAdult); // 19 at the time of this writing...anyone else feel old?
* }
*
* Additionally, we might imagine a lens that produces a different "larger" value on updating than what was given.
* Consider a lens that reads the first string from a list, but produces a Set of strings on update:
*
* {@code
* Lens, Set, String, String> lens = Lens.lens(
* l -> l.get(0),
* (l, s) -> {
* List copy = new ArrayList<>(l);
* copy.set(0, s);
* return new HashSet<>(copy);
* });
*
* String firstElement = view(lens, asList("foo", "bar")); // "foo
* System.out.println(firstElement);
*
* set(lens, "oof", asList("foo", "bar")); // ["bar", "oof"]
* set(lens, "bar", asList("foo", "bar")); // ["bar"]
* }
*
* For more information, learn
* 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
*/
@FunctionalInterface
public interface Lens extends Monad>, Profunctor> {
, FB extends Functor> FT apply(
Function super A, ? extends FB> fn, S s);
/**
* Fix this lens against some functor, producing a non-polymorphic runnable lens as an {@link Fn2}.
*
* 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 common functor type of FT and FB
* @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() {
return this::apply;
}
@Override
default Lens fmap(Function super T, ? extends U> fn) {
return Monad.super.fmap(fn).coerce();
}
@Override
default Lens pure(U u) {
return lens(view(this), (s, b) -> u);
}
@Override
default Lens zip(Applicative, Lens> appFn) {
return Monad.super.zip(appFn).coerce();
}
@Override
default Lens discardL(Applicative> appB) {
return Monad.super.discardL(appB).coerce();
}
@Override
default Lens discardR(Applicative> appB) {
return Monad.super.discardR(appB).coerce();
}
@Override
default Lens flatMap(Function super T, ? extends Monad>> f) {
return lens(view(this), (s, b) -> set(f.apply(set(this, b, s)).>coerce(), b, s));
}
@Override
default Lens diMapL(Function super R, ? extends S> fn) {
return (Lens) Profunctor.super.diMapL(fn);
}
@Override
default Lens diMapR(Function super T, ? extends U> fn) {
return (Lens) Profunctor.super.diMapR(fn);
}
@Override
default Lens diMap(Function super R, ? extends S> lFn, Function super T, ? extends U> rFn) {
return this.mapS(lFn).mapT(rFn);
}
@Override
default Lens contraMap(Function super R, ? extends S> fn) {
return (Lens) Profunctor.super.contraMap(fn);
}
/**
* 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 super R, ? extends S> 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 super T, ? extends U> 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 super A, ? extends C> 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 super Z, ? extends B> fn) {
return andThen(lens(id(), (a, z) -> fn.apply(z)));
}
/**
* Left-to-right composition of lenses. Requires compatibility between S and T.
*
* @param f the other lens
* @param the new "smaller" value to read (previously A)
* @param the new "smaller" update value (previously B)
* @return the composed lens
*/
default Lens andThen(Lens f) {
return f.compose(this);
}
/**
* Right-to-left composition of lenses. Requires compatibility between A and B.
*
* @param g the other lens
* @param the new "larger" value for reading (previously S)
* @param the new "larger" value for putting (previously T)
* @return the composed lens
*/
default Lens compose(Lens g) {
return lens(view(g).fmap(view(this)), (q, b) -> over(g, set(this, b), q));
}
/**
* Static factory method for creating a lens from a getter function and a setter function.
*
* @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
* @return the lens
*/
static Lens lens(Function super S, ? extends A> getter,
BiFunction super S, ? super B, ? extends T> setter) {
return new Lens() {
@Override
@SuppressWarnings("unchecked")
public , FB extends Functor> FT apply(
Function super A, ? extends FB> fn,
S s) {
return (FT) fn.apply(getter.apply(s)).fmap(b -> setter.apply(s, b));
}
};
}
/**
* Static factory method for creating a simple lens from a getter function and a setter function.
*
* @param getter the getter function
* @param setter the setter function
* @param the type of both "larger" values
* @param the type of both "smaller" values
* @return the lens
*/
static Lens.Simple simpleLens(Function super S, ? extends A> getter,
BiFunction super S, ? super A, ? extends S> setter) {
return adapt(lens(getter, setter));
}
/**
* 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
*/
@FunctionalInterface
interface Simple extends Lens {
@Override
default , FA extends Functor> Fixed fix() {
return this::apply;
}
@SuppressWarnings("unchecked")
default Lens.Simple compose(Lens.Simple g) {
return Lens.super.compose(g)::apply;
}
default Lens.Simple andThen(Lens.Simple f) {
return f.compose(this);
}
@SuppressWarnings("unchecked")
static Simple adapt(Lens lens) {
return lens::apply;
}
/**
* 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
*/
@FunctionalInterface
interface Fixed, FA extends Functor>
extends Lens.Fixed {
}
}
/**
* 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 functor unification type between FT and FB
* @param the type of the lifted T
* @param the type of the lifted B
*/
@FunctionalInterface
interface Fixed, FB extends Functor>
extends Fn2, S, FT> {
}
}