/*
* esodata - data structures and other things, of varying utility
* Copyright 2022, Ben Culkin
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package bjc.optics;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import bjc.data.Holder;
import bjc.data.Pair;
/**
* Utility and constructor functions for lenses
*
* @author bjcul
*
*/
public class Lenses {
/**
* Create an immutable lens from a pair of functions.
*
* @param The first type the lens is used on
* @param The second type the lens is used on
* @param The first type the lens focuses on
* @param The second type the lens focuses on.
*
* @param getter The getter for the lens
* @param setter The setter for the lens
*
* @return The lens composed from the two given functions
*/
public static LensX immutable(Function getter,
BiFunction setter) {
return new FunctionalLensX<>(getter, setter);
}
/**
* Create a mutable lens from a pair of functions.
*
* @param The type the lens is used on
* @param The type the lens is focused on
*
* @param getter The getter for the lens
* @param mutator The mutator for the lens
*
* @return The mutable lens composed from the two given functions
*/
public static MutableLens mutable(Function getter,
BiConsumer mutator) {
return new FunctionalMutableLens<>(getter, mutator);
}
/**
* Create a lens that reflects over the value in a holder
*
* @param The first type contained in the holder
* @param The second type contained in the holder
*
* @return A lens that focuses on the value of a holder
*/
public static LensX, Holder, Part1, Part2> holder() {
return immutable((hld) -> hld.getValue(), (hld, val) -> hld.map((vl) -> val));
}
/**
* Create a lens for updating tagged pairs.
*
* TODO: Should this be on a Tagged class that is isomorphic to pair
* instead?
*
* @param The first data type
* @param The second data type
* @param The tag type
*
* @return A lens that operates on the value of a tagged pair.
*/
public static LensX, Pair, A, B> tagged() {
return immutable((par) -> par.getRight(), (par, val) -> par.mapRight((vl) -> val));
}
// Not entirely sure what I was thinking when I wrote this. Pretty sure this
// would need W1 to be a monoid to make much sense
// public static Lens> both(Lens lhs,
// Lens rhs) {
// }
/**
* Creates a lens which focuses on a piece of internal state.
*
* @param The first state type
* @param The second state type
*
* @param val The initial state value
*
* @return A lens that focuses on the given internal state.
*/
public static LensX, ?, A, B> state(A val) {
Holder hold = Holder.of(val);
return immutable((whole) -> hold.getValue(), (whole, vl) -> hold.map((arg) -> vl));
}
/**
* Creates a lens which focuses on a piece of mutable internal state.
*
* @param The state type
*
* @param val The initial state value
*
* @return A lens that focuses on the given internal state.
*/
public static MutableLens, A> stateM(A val) {
Holder hold = Holder.of(val);
return mutable((whole) -> hold.getValue(), (whole, vl) -> hold.replace(vl));
}
/**
* Create a lens that focuses on the sign of an integer
*
* @return A lens that focuses on the sign of an integer
*/
public static Lens sign() {
LensX lensX = immutable((num) -> num >= 0, (num, sgn) -> sgn ? Math.abs(num) : -Math.abs(num));
return (Lens) lensX;
}
}
final class FunctionalLensX implements LensX {
private final BiFunction setter;
private final Function getter;
FunctionalLensX(Function getter, BiFunction setter) {
this.setter = setter;
this.getter = getter;
}
@Override
public P1 get(W1 source) {
return getter.apply(source);
}
@Override
public W2 set(W1 source, P2 val) {
return setter.apply(source, val);
}
}
final class FunctionalMutableLens implements MutableLens {
private final BiConsumer mutator;
private final Function getter;
FunctionalMutableLens(Function getter, BiConsumer mutator) {
this.mutator = mutator;
this.getter = getter;
}
@Override
public Part get(Whole source) {
return getter.apply(source);
}
@Override
public void mutate(Whole source, Part val) {
mutator.accept(source, val);
}
}