All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jnape.palatable.lambda.lens.Lens Maven / Gradle / Ivy

There is a newer version: 5.5.0
Show newest version
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 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 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>> f) { return lens(view(this), (s, b) -> set(f.apply(set(this, b, s)).>coerce(), b, s)); } @Override default Lens diMapL(Function fn) { return (Lens) Profunctor.super.diMapL(fn); } @Override default Lens diMapR(Function fn) { return (Lens) Profunctor.super.diMapR(fn); } @Override default Lens diMap(Function lFn, Function rFn) { return this.mapS(lFn).mapT(rFn); } @Override default Lens contraMap(Function 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 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 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 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 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 getter, BiFunction setter) { return new Lens() { @Override @SuppressWarnings("unchecked") public , FB extends Functor> FT apply( Function 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 getter, BiFunction 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> { } }