Friday, 14 February 2014

Immutable domain and lenses in Java 8 part 2 - experimentation

In the previous post lenses were used to make deep updates to the immutable domain of Monkey, Hat, Feather and Colour.

As an experiment lets remove the getters from the previous post and instead replace them with lenses; that way we have getters and setters on immutable objects that support deep nesting.

Something like this:

Monkey newMonkey = monkey.hat.feather.colour.set(new Colour("white"));

To make this work we'll use a class called Property that is basically an object and a Lens, with get/set/mod methods that simply apply lens methods.

The code

This lens class is the same as the last post:

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);
 }

}

Property is used to bind a lens to an instance of an object:

public class Property<A, B> {

 protected A a;
 protected Lens<A, B> lens;

 public Property(Lens<A, B> lens, A a) {
  this.lens = lens;
  this.a = a;
 }
    
 protected A getA() {
  return a;
 }

 protected Lens<A, B> getLens() {
  return lens;
 }
    
 public B get() {
  return lens.get(a);
 }

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

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

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

}

In this version of Monkey.java a MonkeyBuilder is introduced, not entirely necessary in this example but useful for larger classes.

public class Monkey {
 
 public static Lens<Monkey, Hat> Hat = new Lens<>((monkey) -> monkey._hat, (
   monkey, hat) -> MonkeyBuilder.from(monkey).hat(hat).build());

 public static Lens<Monkey, Colour> Colour = new Lens<>((monkey) -> monkey._colour, (
   monkey, colour) -> MonkeyBuilder.from(monkey).colour(colour).build());
 
 public final HatProperty<Monkey> hat = new HatProperty<>(Hat, this);
 public final ColourProperty<Monkey> colour = new ColourProperty<>(Colour, this);

 private final String _name;
 private final Colour _colour;
 private final Hat _hat;

 public Monkey(String name, Colour colour, Hat hat) {
  this._name = name;
  this._colour = colour;
  this._hat = hat;
 }

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

 public static class MonkeyBuilder {

  private String name;
  private Colour colour;
  private Hat hat;

  public static MonkeyBuilder from(Monkey monkey) {
   return new MonkeyBuilder().name(monkey._name)
     .colour(monkey._colour).hat(monkey._hat);
  }

  public MonkeyBuilder name(String name) {
   this.name = name;
   return this;
  }

  public MonkeyBuilder colour(Colour colour) {
   this.colour = colour;
   return this;
  }

  public MonkeyBuilder hat(Hat hat) {
   this.hat = hat;
   return this;
  }

  public Monkey build() {
   return new Monkey(name, colour, hat);
  }

 }

}

The HatProperty class defined here is used to provide quick access to properties in Hat, this is used in Monkey.java. The same pattern is used in each class.

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));
 
 public final FeatherProperty<Hat> feather = new FeatherProperty<>(Feather, this);
 public final ColourProperty<Hat> colour = new ColourProperty<>(Colour, this);
  
 private final Colour _colour;
 private final Feather _feather;
 
 public Hat(Colour colour, Feather feather) {
  this._colour = colour;
  this._feather = feather;
 }

 @Override
 public String toString() {
  return "Hat [colour=" + _colour + ", feather=" + _feather + "]";
 }
 
 public static class HatProperty<T> extends Property<T, Hat> {
  
  public HatProperty(Lens<T, Hat> lens, T a) {
   super(lens, a);
  }

  public FeatherProperty<T> feather = new FeatherProperty<T>(getLens().andThen(Hat.Feather), getA());

  public Property<T, Colour> colour = this.andThen(Hat.Colour);

 };

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

 private final Colour _colour;

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

 @Override
 public String toString() {
  return "Feather [colour=" + _colour + "]";
 }
 
 public static class FeatherProperty<T> extends Property<T, Feather> {
  
  public FeatherProperty(Lens<T, Feather> lens, T a) {
   super(lens, a);
  }

  public Property<T, Colour> colour = this.andThen(Feather.Colour);
  
 };
 
}
public class Colour {
 
 public static Lens<Colour, String> Name = new Lens<>(
   (colour) -> colour._name,
   (colour, name) -> new Colour(name));
 
 public final Property<Colour, String> name = new Property<Colour, String>(Name, this);

 private final String _name;

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

 @Override
 public String toString() {
  return "Colour [colour=" + _name + "]";
 }
 
 public static class ColourProperty<T> extends Property<T, Colour> {

  public ColourProperty(Lens<T, Colour> lens, T a) {
   super(lens, a);
  }
  
  public Property<T, String> name = this.andThen(Name);
  
 }

}

Example of how this all works:

Monkey monkey = new Monkey("Bob", new Colour("brown"), new Hat(new Colour("yellow"), new Feather(new Colour("blue"))));
System.out.println(monkey);
System.out.println(Monkey.Hat.andThen(Hat.Feather).andThen(Feather.Colour).get(monkey));
monkey = monkey.hat.feather.colour.set(new Colour("white"));
System.out.println(monkey);
monkey = monkey.hat.colour.set(new Colour("red"));
System.out.println(monkey);

The output:

Monkey [name=Bob, colour=Colour [colour=brown], hat=Hat [colour=Colour [colour=yellow], feather=Feather [colour=Colour [colour=blue]]]]
Colour [colour=blue]
Monkey [name=Bob, colour=Colour [colour=brown], hat=Hat [colour=Colour [colour=yellow], feather=Feather [colour=Colour [colour=white]]]]
Monkey [name=Bob, colour=Colour [colour=brown], hat=Hat [colour=Colour [colour=red], feather=Feather [colour=Colour [colour=white]]]]

No comments:

Post a Comment