Skip to content

Instantly share code, notes, and snippets.

@SegFaultAX
Last active June 20, 2018 00:49
Show Gist options
  • Save SegFaultAX/0406c7a3b9ffe5aef0d2b557b42bbd4a to your computer and use it in GitHub Desktop.
Save SegFaultAX/0406c7a3b9ffe5aef0d2b557b42bbd4a to your computer and use it in GitHub Desktop.
Example of van Laarhoven Lenses [Java]
import java.util.function.Function;
public class Const<A, B> implements Functor<B> {
private final A value;
private Const(A value) {
this.value = value;
}
public static <A, B> Const<A, B> of(A value) {
return new Const<>(value);
}
public A run() {
return value;
}
@Override
public <C> Functor<C> fmap(Function<B, C> f) {
return of(value);
}
}
import java.util.function.Function;
public interface Functor<A> {
<B> Functor<B> fmap(Function<A, B> f);
}
import java.util.function.Function;
public class Identity<A> implements Functor<A> {
private final A value;
private Identity(A value) {
this.value = value;
}
public static <A> Identity<A> of(A value) {
return new Identity<>(value);
}
public A run() {
return value;
}
@Override
public <B> Functor<B> fmap(Function<A, B> f) {
return of(f.apply(value));
}
}
import java.util.function.BiFunction;
import java.util.function.Function;
@FunctionalInterface
public interface Lens<S, T, A, B> extends BiFunction<Function<A, Functor<B>>, S, Functor<T>> {
@Override
Functor<T> apply(Function<A, Functor<B>> aFunctorFunction, S s);
default T over(Function<A, B> f, S s) {
return Lens.over(this, f, s);
}
default A view(S s) {
return Lens.view(this, s);
}
default T set(B value, S s) {
return Lens.set(this, value, s);
}
static <S, T, A, B> T over(Lens<S, T, A, B> lens, Function<A, B> f, S s) {
Function<A, Functor<B>> mutate = v -> Identity.of(f.apply(v));
Functor<T> result = lens.apply(mutate, s);
return ((Identity<T>) result).run();
}
static <S, T, A, B> A view(Lens<S, T, A, B> lens, S s) {
return ((Const<A, Void>) lens.apply(Const::of, s)).run();
}
static <S, T, A, B> T set(Lens<S, T, A, B> lens, B value, S s) {
return over(lens, _v -> value, s);
}
static <S, T, A, B, X, Y> Lens<S, T, X, Y> compose(Lens<S, T, A, B> f, Lens<A, B, X, Y> g) {
return (inj, s) -> f.apply(a -> g.apply(inj, a), s);
}
}
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Objects;
import org.junit.Test;
public class LensTest {
public static class Person {
private final String name;
private final int age;
public static MonoLens<Person, String> _name =
(inj, person) -> inj.apply(person.getName()).fmap(newName -> new Person(newName, person.getAge()));
public static MonoLens<Person, Integer> _age =
(inj, person) -> inj.apply(person.getAge()).fmap(newAge -> new Person(person.getName(), newAge));
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if ((o == null) || (getClass() != o.getClass())) {
return false;
}
Person person = (Person) o;
return (age == person.age) && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
public static class Assignment {
private final Person person;
private final String name;
public static final MonoLens<Assignment, Person> _person =
(inj, assi) -> inj.apply(assi.getPerson()).fmap(newPerson -> new Assignment(newPerson, assi.getName()));
public static final MonoLens<Assignment, String> _name =
(inj, assi) -> inj.apply(assi.getName()).fmap(newName -> new Assignment(assi.getPerson(), newName));
public Assignment(Person person, String name) {
this.person = person;
this.name = name;
}
public Person getPerson() {
return person;
}
public String getName() {
return name;
}
}
public static class Pair<A, B> {
public final A a;
public final B b;
public static <A, B, C> Lens<Pair<A, C>, Pair<B, C>, A, B> _1() {
return (inj, pair) -> inj.apply(pair.a).fmap(newVal -> of(newVal, pair.b));
}
public static <A, B, C> Lens<Pair<A, B>, Pair<A, C>, B, C> _2() {
return (inj, pair) -> inj.apply(pair.b).fmap(newVal -> of(pair.a, newVal));
}
public static <A, B> Pair<A, B> of(A a, B b) {
return new Pair<>(a, b);
}
private Pair(A a, B b) {
this.a = a;
this.b = b;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Pair<?, ?> pair = (Pair<?, ?>) o;
return Objects.equals(a, pair.a) &&
Objects.equals(b, pair.b);
}
@Override
public int hashCode() {
return Objects.hash(a, b);
}
@Override
public String toString() {
return "Pair{" +
"a=" + a +
", b=" + b +
'}';
}
}
@Test
public void testOver() {
Person per = new Person("Jim", 30);
assertThat(Lens.over(Person._age, age -> age + 10, per).getAge()).isEqualTo(40);
}
@Test
public void testView() {
Person per = new Person("Jim", 30);
assertThat(Lens.view(Person._name, per)).isEqualTo("Jim");
}
@Test
public void testSet() {
Person per = new Person("Jim", 30);
assertThat(Lens.set(Person._name, "Amy", per).getName()).isEqualTo("Amy");
}
@Test
public void testComposition() {
Person per = new Person("Jim", 30);
Assignment assi = new Assignment(per, "simple");
assertThat(Lens.view(Lens.compose(Assignment._person, Person._name), assi)).isEqualTo("Jim");
}
@Test
public void testThreeLawsSafe() {
Person per = new Person("Jim", 30);
// 1. You get back what you put in
assertThat(Person._name.view(Person._name.set("Amy", per))).isEqualTo("Amy");
// 2. Putting back what you got doesn't change anything
assertThat(Person._name.set(Person._name.view(per), per)).isEqualTo(per);
// 3. Setting twice is the same as setting once
assertThat(Person._name.set("Amy", Person._name.set("Jane", per))).isEqualTo(new Person("Amy", 30));
}
@Test
public void testPolymorphicLens() {
Pair<String, Integer> p1 = Pair.of("Jim", 30);
assertThat(Pair.<String, Integer, Integer>_1().over(String::length, p1))
.isEqualTo(Pair.of(3, 30));
assertThat(Pair.<String, Integer, String>_2().over(v -> Integer.toString(v), p1))
.isEqualTo(Pair.of("Jim", "30"));
Lens<Pair<String, Integer>, Person, String, String> firstPerson =
(inj, pair) -> inj.apply(pair.a).fmap(name -> new Person(name, 30));
assertThat(firstPerson.over(name -> name + "!", p1)).isEqualTo(new Person("Jim!", 30));
}
}
@FunctionalInterface
public interface MonoLens<S, A> extends Lens<S, S, A, A> {
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment