Friday 14 February 2014

Immutable domain and lenses in Java 8

The domain used in this post consists of a Monkey, Hat, Feather and Colour. It will be entirely immutable and use lenses to make changes (that is copies with changes) of the data.

The difficulty with an immutable domain is that to change the colour of the feather in the monkey's hat, we need to create a new instance of Monkey, Hat and Feather; this is where lenses come in. A lense is basically a getter/setter that can be used for deep updates of immutable data.

For this example the following lenses are defined, the full classes are given further down.


Monkey:
 public static Lens<Monkey, Hat> Hat = new Lens<>(
   (monkey) -> monkey.hat,
   (monkey, hat) -> new Monkey(monkey.name, hat));

Hat:
 public static Lens<Hat, Feather> Feather = new Lens<>(
   (hat) -> hat.feather,
   (hat, feather) -> new Hat(hat.colour, feather));

Feather:
 public static Lens<Feather, Colour> Colour = new Lens<>(
   (feather) -> feather.colour,
   (feather, colour) -> new Feather(colour));

These lenses can be used to update the colour of the feather in the monkey's hat like this:


Monkey newMonkey = Monkey.Hat.andThen(Hat.Feather).andThen(Feather.Colour).set(monkey, new Colour("white"));

Now for all the code

This lens definition is a modified version of https://github.com/remeniuk/java-lenses for Java 8.

public class Lens<A, B> {

 public Function<A, B> fget;
 public BiFunction<A, B, A> fset;

 public Lens(Function<A, B> fget, BiFunction<A, B, A> fset) {
  this.fget = fget;
  this.fset = fset;
 }

 public B get(A a) {
  return fget.apply(a);
 }

 public A set(A a, B b) {
  return fset.apply(a, b);
 }

 public A mod(A a, Function<B, B> f) {
  return set(a, f.apply(get(a)));
 }

 public <C> Lens<C, B> compose(final Lens<C, A> that) {
  return new Lens<C, B>(
    (c) -> get(that.get(c)),
    (c, b) -> that.mod(c, (a) -> set(a, b)));
 }

 public <C> Lens<A, C> andThen(Lens<B, C> that) {
  return that.compose(this);
 }

}

These are the classes that make up the domain model.

public class Monkey {
 
 public static Lens<Monkey, Hat> Hat = new Lens<>(
   (monkey) -> monkey.hat,
   (monkey, hat) -> new Monkey(monkey.name, hat));

 private final String name;
 private final Hat hat;

 public Monkey(String name, Hat hat) {
  this.name = name;
  this.hat = hat;
 }

 public String getName() {
  return name;
 }

 public Hat getHat() {
  return hat;
 }

 @Override
 public String toString() {
  return "Monkey [name=" + name + ", hat="
    + hat + "]";
 }

}
public class Hat {

 public static Lens<Hat, Feather> Feather = new Lens<>(
   (hat) -> hat.feather,
   (hat, feather) -> new Hat(hat.colour, feather));

 public static Lens<Hat, Colour> Colour = new Lens<>((hat) -> hat.colour,
   (hat, colour) -> new Hat(colour, hat.feather));
  
 private final Colour colour;
 private final Feather feather;
 
 public Hat(Colour colour, Feather feather) {
  this.colour = colour;
  this.feather = feather;
 }

 public Colour getColour() {
  return colour;
 }

 public Feather getFeather() {
  return feather;
 }

 @Override
 public String toString() {
  return "Hat [colour=" + colour + ", feather=" + feather + "]";
 }

}
public class Feather {
 
 public static Lens<Feather, Colour> Colour = new Lens<>(
   (feather) -> feather.colour,
   (feather, colour) -> new Feather(colour));

 private final Colour colour;

 public Feather(Colour colour) {
  this.colour = colour;
 }

 public Colour getColour() {
  return colour;
 };
 
 @Override
 public String toString() {
  return "Feather [colour=" + colour + "]";
 }
 
}
public class Colour {
 
 public static Lens<Colour, String> Name = new Lens<>(
   (colour) -> colour.name,
   (colour, name) -> new Colour(name));
 
 private final String name;

 public Colour(String name) {
  this.name = name;
 }

 public String getName() {
  return name;
 }

 @Override
 public String toString() {
  return "Colour [colour=" + name + "]";
 }

}

Now for a slightly more complete example of usage:

Monkey monkey = new Monkey("Ham", new Hat(new Colour("yellow"), new Feather(new Colour("blue"))));
System.out.println(monkey);
System.out.println(Monkey.Hat.andThen(Hat.Colour).get(monkey));
monkey = Monkey.Hat.andThen(Hat.Feather).andThen(Feather.Colour).set(monkey, new Colour("white"));
System.out.println(monkey);

Output:

Monkey [name=Ham, hat=Hat [colour=Colour [colour=yellow], feather=Feather [colour=Colour [colour=blue]]]]
Colour [colour=yellow]
Monkey [name=Ham, hat=Hat [colour=Colour [colour=yellow], feather=Feather [colour=Colour [colour=white]]]]

No comments:

Post a Comment