Created
December 2, 2015 08:19
-
-
Save blendmaster/dd54aad660374c159642 to your computer and use it in GitHub Desktop.
van laarhoven lenses in java
This file contains 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.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