Skip to content

Instantly share code, notes, and snippets.

@blendmaster
Created December 2, 2015 08:19
Show Gist options
  • Save blendmaster/dd54aad660374c159642 to your computer and use it in GitHub Desktop.
Save blendmaster/dd54aad660374c159642 to your computer and use it in GitHub Desktop.
van laarhoven lenses in java
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.*;
/**
* Fancy Lenses Like The Ones In Haskell
*/
class FancyLenses {
// You've got yourself some fine immutable JavaBeans™, and you're cool with that.
class Contact {
final Name name;
final String notes;
Contact(Name name, String notes) {
this.name = name;
this.notes = notes;
}
}
class Name {
final String givenName;
final String familyName;
Name(String givenName, String familyName) {
this.givenName = givenName;
this.familyName = familyName;
}
}
{
Contact contact = new Contact(new Name("Bob", "Sapp"), "It's Sapp Time!");
// gettin' nested values is fine:
assert contact.name.givenName.equals("Bob");
// but settin' leaves a bit to be desired, compared to mutable:
// contact.name.familyName = "Zapp";
contact = new Contact(new Name(contact.name.givenName, "Zapp"), contact.notes);
}
// You're hip to the game though, check out this Cool Thing:
class Lens<STRUCT, FIELD> {
final Function<STRUCT, FIELD> getter;
//takes an old STRUCT, a new FIELD and returns a new STRUCT with the new FIELD.
final BiFunction<STRUCT, FIELD, STRUCT> setter;
Lens(Function<STRUCT, FIELD> getter, BiFunction<STRUCT, FIELD, STRUCT> setter) {
this.getter = getter;
this.setter = setter;
}
// this is the cool part
<NESTED_FIELD> Lens<STRUCT, NESTED_FIELD> join(Lens<FIELD, NESTED_FIELD> innerLens) {
return new Lens<>(
this.getter.andThen(innerLens.getter),
(struct, newNestedField) ->
this.setter.apply(
struct,
innerLens.setter.apply(this.getter.apply(struct), newNestedField)));
}
}
// it's a combination of a getter and (immutable-style) setter:
Lens<Name, String> givenNameLens = new Lens<>(
name -> name.givenName,
(currentName, newGivenName) -> new Name(newGivenName, currentName.familyName));
Lens<Contact, Name> nameLens = new Lens<>(
contact -> contact.name,
(currentContact, newName) -> new Contact(newName, currentContact.notes));
// but composable:
Lens<Contact, String> contactGivenNameLens = nameLens.join(givenNameLens);
{
Contact contact = new Contact(new Name("Bob", "Ross"), "Happy lil' trees");
assert nameLens.join(givenNameLens).getter.apply(contact).equals("Bob");
// almost symmetrically
contact = nameLens.join(givenNameLens).setter.apply(contact, "Crab");
// squint and you'll see:
// contact.name.givenName == "Bob";
// contact.name.givenName = "Crab";
}
// Cooler yet is you can write lenses for things that aren't really traditional fields:
Lens<String, Character> charAtLens(int index) {
return new Lens<>(
string -> string.charAt(index),
(currentString, newCharAt) -> {
StringBuilder builder = new StringBuilder(currentString);
builder.setCharAt(index, newCharAt);
return builder.toString();
});
}
{
Contact contact = new Contact(new Name("Bob", "Dylan"), "some sort of stone roller");
// but they still join along as if they were:
Lens<Contact, Character> thirdChar = nameLens.join(givenNameLens).join(charAtLens(2));
assert thirdChar.getter.apply(contact).equals('b');
Contact futureFolk = thirdChar.setter.apply(contact, 't');
// squint at:
// contact.name.givenName.charAt(2) == 'b';
// contact.name.givenName.charAt(2) = 't'; // #woah #wow
}
// (The Lens class you wrote actually can take you pretty far, once you smooth over
// the syntax warts with more helper functions, and build up a standard library
// of useful lens-makers, like listElementAt, filteredElementsOfAList, etc.)
// Kinda sucks that you have bundle together two functions though. But, it turns out
// that if you cheat, you _can_ combine the getter and the setter.
// If you generalize the concept of setting to _updating_ a field
// based on its previous value, and curry the arguments in a weird way:
// (this is a poor man's typedef)
interface Updater<STRUCT, FIELD>
// if you give me a field updater function, Function<FIELD, FIELD>
// I'll give you a function that updates the entire struct, Function<STRUCT, STRUCT>
extends Function<Function<FIELD, FIELD>, Function<STRUCT, STRUCT>> {}
// the definition is still pretty straightforward thanks to lambda syntax:
Updater<Contact, Name> nameUpdater = nameUpdater -> currentContact ->
new Contact(nameUpdater.apply(currentContact.name), currentContact.notes);
Updater<Name, String> givenNameUpdater = givenNameUpdater -> currentName ->
new Name(givenNameUpdater.apply(currentName.givenName), currentName.familyName);
// but now you get the "join" method for free:
Function<Function<String, String>, Function<Contact, Contact>> contactNameUpdaterFunction =
nameUpdater.compose(givenNameUpdater);
// cheat with ::apply method reference to get the typedef back:
Updater<Contact, String> contactNameUpdater = nameUpdater.compose(givenNameUpdater)::apply;
{
Contact contact = new Contact(new Name("Robert", "Frost"), "Fresh 'outta Bobs.");
// setting is just updating while ignoring the passed in value:
contact = nameUpdater.compose(givenNameUpdater)
.apply(ignoredCurrentValue -> "Bobby")
.apply(contact);
// and getting is this hack, since you're No Stranger To Mutability:
AtomicReference<String> got = new AtomicReference<>();
nameUpdater.compose(givenNameUpdater)
.apply(currentValue -> {
got.set(currentValue); // sneak away current value
return currentValue; // pretend nothing happened
})
.apply(contact);
assert got.get().equals("Bobby");
}
// wrapped up in generic functions, you get:
<STRUCT, FIELD> Function<STRUCT, FIELD> getter(Updater<STRUCT, FIELD> updater) {
return struct -> {
AtomicReference<FIELD> got = new AtomicReference<>();
updater
.apply(currentField -> {
got.set(currentField);
return currentField;
})
.apply(struct);
return got.get();
};
}
<STRUCT, FIELD> BiFunction<STRUCT, FIELD, STRUCT> setter(Updater<STRUCT, FIELD> updater) {
return (struct, newField) ->
updater.apply(ignoredCurrentField -> newField).apply(struct);
}
// this is cool, but one annoying thing is that the getter recreates the object when
// you `.apply(struct)` again, even though you didn't change anything. Luckily,
// you can hack that away too, by adding a Layer Of Indirection™:
interface UpdaterSupplier<STRUCT, FIELD>
// if you give me an function that will supply an updated field,
// Function<FIELD, Supplier<FIELD>>,
// I'll give you a function that will supply an updated struct,
// Function<STRUCT, Supplier<STRUCT>>
extends Function<Function<FIELD, Supplier<FIELD>>, Function<STRUCT, Supplier<STRUCT>>> {}
// now, if you carefully construct your updater suppliers:
UpdaterSupplier<Contact, Name> nameUpdaterSupplier = nameUpdater -> currentContact -> {
Supplier<Name> lazyNewName = nameUpdater.apply(currentContact.name);
return () -> new Contact(lazyNewName.get(), currentContact.notes);
};
UpdaterSupplier<Name, String> givenNameUpdaterSupplier = givenNameUpdater -> currentName -> {
Supplier<String> lazyNewGivenName = givenNameUpdater.apply(currentName.givenName);
return () -> new Name(lazyNewGivenName.get(), currentName.familyName);
};
// you can _still_ join the lenses fo' free:
UpdaterSupplier<Contact, String> contactGivenNameUpdaterSupplier =
nameUpdaterSupplier.compose(givenNameUpdaterSupplier)::apply;
{
Contact contact = new Contact(new Name("Al", "Gore"), "the internet?");
// but now when you cheat your setter, you can avoid fully creating another Contact
// (instead, you get a supplier that you can ignore)
AtomicReference<String> got = new AtomicReference<>();
Supplier<Contact> ignorableSupplier =
contactGivenNameUpdaterSupplier
.apply(currentGivenName -> {
got.set(currentGivenName);
return () -> currentGivenName;
})
.apply(contact);
assert got.get().equals("Al");
// (note: yes, you just traded a `new Contact` allocation for a
// Supplier<Contact> allocation, but you'll fix that later)
}
// generically again:
<STRUCT, FIELD> Function<STRUCT, FIELD>
updaterSupplierGetter(UpdaterSupplier<STRUCT, FIELD> updater) {
return struct -> {
AtomicReference<FIELD> got = new AtomicReference<>();
updater
.apply(currentField -> {
got.set(currentField);
return () -> currentField;
})
.apply(struct);
return got.get();
};
}
// and setter for good measure:
<STRUCT, FIELD> BiFunction<STRUCT, FIELD, STRUCT>
updaterSupplierSetter(UpdaterSupplier<STRUCT, FIELD> updater) {
return (struct, newField) ->
updater.apply(ignoredCurrentField -> () -> newField).apply(struct).get();
}
// Kinda cool, but that weird way you had to write the updaters is bothersome.
// Using {} and `return` in lambdas is lame.
// However, you can extract out the pattern of doing stuff to a value,
// but only when you actually want it:
interface Lazy<T> extends Supplier<T> {
// apply function to the value inside us, but later, when .get() is actually called.
default <R> Lazy<R> applyLater(Function<T, R> fn) {
return () -> fn.apply(this.get());
}
}
interface LazyUpdater<STRUCT, FIELD>
// if you give me an function that will lazily update a field,
// Function<FIELD, Lazy<FIELD>>,
// I'll give you a function that will lazily update a struct,
// Function<STRUCT, Supplier<STRUCT>>
extends Function<Function<FIELD, Lazy<FIELD>>, Function<STRUCT, Lazy<STRUCT>>> {}
// now you can write your updaters in a bit more regular manner:
LazyUpdater<Contact, Name> lazyNameUpdater = nameUpdater -> currentContact ->
nameUpdater.apply(currentContact.name).applyLater(newName ->
new Contact(newName, currentContact.notes));
LazyUpdater<Name, String> lazyGivenNameUpdater = givenNameUpdater -> currentName ->
givenNameUpdater.apply(currentName.givenName).applyLater(newGivenName ->
new Name(newGivenName, currentName.familyName));
// still composes:
LazyUpdater<Contact, String> lazyContactGivenNameUpdater =
lazyNameUpdater.compose(lazyGivenNameUpdater)::apply;
// skipping the lame contact examples for now, here's the generic getter and setter:
<STRUCT, FIELD> Function<STRUCT, FIELD>
lazyGetter(LazyUpdater<STRUCT, FIELD> updater) {
return struct -> {
AtomicReference<FIELD> got = new AtomicReference<>();
updater
.apply(currentField -> {
got.set(currentField);
return () -> currentField;
})
.apply(struct);
return got.get();
};
}
<STRUCT, FIELD> BiFunction<STRUCT, FIELD, STRUCT>
lazySetter(LazyUpdater<STRUCT, FIELD> updater) {
return (struct, newField) ->
updater.apply(ignoredCurrentField -> () -> newField).apply(struct).get();
}
// (there aren't actually any changes from before other than the input type)
// But now, look back at an updater function:
LazyUpdater<Name, String> lazyFamilyNameUpdater = familyNameUpdater -> currentName ->
familyNameUpdater.apply(currentName.familyName).applyLater(newFamilyName ->
new Name(currentName.givenName, newFamilyName));
// it really only depends on the `Lazy.applyLater` call, not the `.get()` call anymore.
// Thinking back to avoiding that object allocation, `applyLater` traditionally
// has to return a whole new supplier. However, if you're not going call .get() anyway
// (and instead just sneak your AtomicReference.set), you can just write:
static class ReallyLazy<FAKE> implements Lazy<FAKE> {
static final ReallyLazy<?> INSTANCE = new ReallyLazy<>();
@Override
public FAKE get() {
throw new AssertionError("never called");
}
@SuppressWarnings("unchecked")
@Override
public <R> Lazy<R> applyLater(Function<FAKE, R> fn) {
// yeah, sure, whatever
return (Lazy<R>) INSTANCE;
}
}
@SuppressWarnings("unchecked")
<STRUCT, FIELD> Function<STRUCT, FIELD>
reallyLazyGetter(LazyUpdater<STRUCT, FIELD> updater) {
return struct -> {
AtomicReference<FIELD> got = new AtomicReference<>();
Lazy<STRUCT> ignored = updater
.apply(currentField -> {
got.set(currentField);
return (Lazy<FIELD>) ReallyLazy.INSTANCE;
})
.apply(struct);
return got.get();
};
}
// you saved 1 allocation, neat. You can go deeper, though.
// Having to do the whole AtomicReference runaround is annoyingly {} and returny.
// But, since you now have a custom ReallyLazy class anyway, you can just
// tack on the reference hiding functionality:
static class HiddenLazy<HIDDEN, FAKE> implements Lazy<FAKE> {
final HIDDEN hidden;
HiddenLazy(HIDDEN hidden) {
this.hidden = hidden;
}
@Override
public FAKE get() {
throw new AssertionError("never called");
}
@SuppressWarnings("unchecked")
@Override
public <FAKE2> HiddenLazy<HIDDEN, FAKE2> applyLater(Function<FAKE, FAKE2> fn) {
// psst, pass this along and pretend you did something
return (HiddenLazy<HIDDEN, FAKE2>) this;
}
}
// and setter is now:
@SuppressWarnings("unchecked")
<STRUCT, FIELD> Function<STRUCT, FIELD>
hiddenLazyGetter(LazyUpdater<STRUCT, FIELD> updater) {
return struct ->
((HiddenLazy<FIELD, ?>) updater
// tuck away the current value
.apply(currentField -> new HiddenLazy<>(currentField))
// doesn't actually do anything here
.apply(struct))
// then after the unfortunate cast, just pop our hidden value back out.
.hidden;
}
// Looking back, your setter is suspiciously similar:
<STRUCT, FIELD> BiFunction<STRUCT, FIELD, STRUCT>
lazySetterAgain(LazyUpdater<STRUCT, FIELD> updater) {
return (struct, newField) ->
updater
// wrap the new field value in a Lazy, (() -> new Field)
.apply(ignoredCurrentField -> () -> newField)
.apply(struct)
// finally grab the value back out
.get();
}
// you could make it almost exactly similar, in fact:
static class NotSoLazy<T> implements Lazy<T> {
final T value;
NotSoLazy(T value) {
this.value = value;
}
@Override
public T get() {
throw new AssertionError("never called");
}
@Override
public <R> NotSoLazy<R> applyLater(Function<T, R> fn) {
// just apply the function right now, we're actually doing work.
return new NotSoLazy<>(fn.apply(value));
}
}
// and now:
<STRUCT, FIELD> BiFunction<STRUCT, FIELD, STRUCT>
notSoLazySetter(LazyUpdater<STRUCT, FIELD> updater) {
return (struct, newField) ->
((NotSoLazy<STRUCT>) updater
// wrap the new field value, a.k.a. construct
.apply(ignoredCurrentField -> new NotSoLazy<>(newField))
// actually applies the updating function, wrapped in NotSoLazy
.apply(struct))
// finally grab the updated struct back out of the wrapper
.value;
}
// check out that symmetry! (and pay no attention to the cast behind the curtain)
// cleaning up a little, it turns out that all this stuff has names in Haskell:
// Lazy (minus the spurious Supplier superclass) is in fact our friend Functor
interface Functor<T> {
<R> Functor<R> fmap(Function<T, R> fn);
}
// NotSoLazy just applies the functions and is called the Identity functor:
static class Identity<T> implements Functor<T> {
final T value;
Identity(T value) {
this.value = value;
}
@Override
public <R> Identity<R> fmap(Function<T, R> fn) {
return new Identity<>(fn.apply(value));
}
}
// and HiddenLazy is Const, because the hidden part stays constant:
static class Const<T, FAKE> implements Functor<FAKE> {
final T value;
Const(T value) {
this.value = value;
}
@SuppressWarnings("unchecked")
@Override
public <FAKE2> Const<T, FAKE2> fmap(Function<FAKE, FAKE2> fn) {
// hey, I'm applying myself
return (Const<T, FAKE2>) this;
}
}
// and the good ol' LazyUpdate is in fact a fancy Van Laarhoven Lens:
interface VLLens<STRUCT, FIELD> extends
Function<Function<FIELD, Functor<FIELD>>,
Function<STRUCT, Functor<STRUCT>>> {
// put the getter (view in haskell parlance) and setter (set) as default
// methods this time, for sugar:
@SuppressWarnings("unchecked")
default FIELD get(STRUCT struct) {
return ((Const<FIELD, STRUCT>) apply(Const::new).apply(struct)).value;
}
@SuppressWarnings("unchecked")
default STRUCT update(STRUCT struct, Function<FIELD, FIELD> updater) {
return ((Identity<STRUCT>) apply(updater.andThen(Identity::new)).apply(struct)).value;
}
default STRUCT set(STRUCT struct, FIELD newField) {
return update(struct, ignoredOldField -> newField);
}
// specialized compose for our poor typedef
default <SUBFIELD> VLLens<STRUCT, SUBFIELD> join(VLLens<FIELD, SUBFIELD> other) {
return this.compose(other)::apply;
}
}
// and yes, it does work:
VLLens<Contact, Name> _name = nameUpdater -> currentContact ->
nameUpdater.apply(currentContact.name).fmap(newName ->
new Contact(newName, currentContact.notes));
VLLens<Name, String> _givenName = givenNameUpdater -> currentName ->
givenNameUpdater.apply(currentName.givenName).fmap(newGivenName ->
new Name(newGivenName, currentName.familyName));
VLLens<Contact, String> _contactGivenName = _name.compose(_givenName)::apply;
{
Contact contact = new Contact(new Name("Al", "Yankovic"), "That's Als, folks");
Contact pal = _name.join(_givenName).set(contact, "Pal");
assert _name.join(_givenName).get(pal).equals("Pal");
// squint like you've never squinted before:
// contact.name.givenName = "Pal";
// assert contact.name.givenName == "Pal";
}
// So yeah, haskell has fancy lenses, but they're really just
// disguising an AtomicReference mutation. Tell all your friends.
// Exercise for the reader/java type system nerd snipe:
// implement VLLens _without_ unchecked casts.
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment