Last active
June 20, 2018 00:49
-
-
Save SegFaultAX/0406c7a3b9ffe5aef0d2b557b42bbd4a to your computer and use it in GitHub Desktop.
Example of van Laarhoven Lenses [Java]
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.function.Function; | |
public interface Functor<A> { | |
<B> Functor<B> fmap(Function<A, B> f); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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