On Fluent Interfaces

A fluent (programming) interface is one that is used by chaining method calls.

Suppose we need to create a tool constructing and printing English sentences. Here are some possible programming interfaces to that tool:

Methods:

public void printSentence(Person subject Action action, Object object, Time when);
// used as:
printSentence(john, Action.eat, Fruit.apple, Time.morning);
printSentence(mary, Action.sing, Song.jingleBells, /*Unspecified time */null);

Setters:

Sentence sentence = new Sentence();
sentence.setSubject(john);
sentence.setAction(Action.eat);
sentence.setObject(Fruit.apple);
sentence.setTime(Time.morning);
sentence.print();

Inheritance:

new AbstractSentence() {
    public Person getSubject() { return mary; }
    public Action getAction() { return Action.sing; }
    public Object getObject() { return Song.jingleBells; }
/* getTime() not overriden → unspecified time */
].print();

Fluent:

subject(mary)
    .does(Action.sing)
    .on(Song.jingleBells)
    .print();

These programming styles all have advantages and disadvantages but the topic of this post is not to help you choose between them. Suppose we have already chosen to implement a fluent interface, how should it be defined to be Level One against incorrect use?

The next sections will examine several requirements that qualify what “incorrect use” means.

Required Method Call

“The does method must be called before invoking print

Level Six: set a boolean flag when does is called, and check it is true when print is called.

public class SentenceBuilder {
    private boolean doesCalled = false;
    public SentenceBuilder does(Action action) {
        doesCalled = true;
        // ...
         return this;
    }
    public void print() {
        Preconditions.checkState(doesCalled, "You must call does() before invoking print()");
         // ...
   }
}
// example use:
subject(john).action(Action.run).print();
// example misuse:
subject(john).print(); // ← throws an IllegalStateException

Level One: Put the print method in a (different) class returned by does.

public class SentenceBuilder {
    Person subject;
    Action action;
    public static Subject subject(Person subject) {
        return new Base(subject);
    }
    public class Base {
        protected Base() {}
        private Base(Person subject) { SentenceBuilder.this.subject = subject; }
        public Does does(Action action) {
            return new Does(action);
        }
        // also define on() and time() here.
    }
    public class Does extends Base {
        private Does(Action action) {
            SentenceBuilder.this.action = action;
        }
        public void print() {
            // ...
        }
    }
}
// example use:
subject(john).does(Action.run).print();
// example misuse:
subject(john).print(); // error: Base has no method named print

Type Correlation

“In does(Action.eat).on(x), x must be a Food item, in does(Action.sing).on(x), x must be a Song item, etc”.

Level Seven, a simple definition of SentenceBuilder defines on as follows:

public SentenceBuilder on(Object object) {
    this.object = object;
    return this;
}

Parameter type Object permits passing Food or Song but does not prevent this:

subject(john).does(Action.eat).on(Song.jingleBells);

Level Six, check the type with a switch:

public enum Action {
    eat(Food.class), sing(Song.class), ...;
    public final Class<?> object;
    private Action(Class<?> object) { this.object = object; }
}

SentenceBuilder.on() implementation:

public SentenceBuilder on(Object object) {
    Preconditions.checkArgument(object.getClass() == action.object,
        Action +" requires an object of type "+ action.object.getSimpleName() +
        ", got "+ object.getClass().getSimpleName() +" instead");
    // ...
}

Now doing this:

subject(john).does(Action.eat).on(Song.jingleBells);

will throw IllegalArgumentException: Action eat requires an object of type Food, got Song instead.

Level One, one method per Action.

public SentenceBuilder eats(Food f) {
    return does(Action.eat, f);
}
public SentenceBuilder sings(Song s) {
    return does(Action.sing, s);
}
private SentenceBuilder does(Action a, Object o) {
    this.action = a;
    this.object = o;
    return this;
}
// Example use:
subject(john).eats(Fruit.banana);
// Example misuse:
subject(john).eats(Song.jingleBells); // ← error: Song can't be cast to Food

That option requires duplicating all Action entries with methods. But does Action really have to be an enum?

Level One, Typed Constants and Typed Continuations:

public class Action<O> {
    public static final Action<Food> eat = new Action<>();
    public static final Action<Song> sing = new Action<>();
    private Action() {}
// omitted: ways to "switch" actions, get their names, etc
}
public class SentenceBuilder {
    // move these two into 'Does' if you need to remember the types
    Action<?> action;
    Object object;
    // ...
    public class Base {
        public <O> Does<O> does(Action<O> action) {
            return new Does<>(action);
        }
        // more common methods
    }
    public class Does<O> extends Base {
        // constructor...
        public Does<O> on(O object) {
            // ...
        }
    }
}
// Example use: (does() returns SentenceBuilder.Does<Food>)
subject(john).does(Action.eat).on(Fruit.banana);
// Example misuse: ("error: Song can't be cast to Food")
subject(john).does(Action.eat).on(Song.jingleBells);

Return Type Transformers

Suppose, instead of printing a String to standard output, the default behaviour of the SentenceBuilder.print() method were to return a String. Suppose also that the SentenceBuilder retains the raw data in a “Sentence” (the Person instance, the Action instance, etc, as opposed to immediately piling it all into a StringBuilder). The SentenceString conversion is done by a Function<Sentence, String>. Now we want to add a outputWith(Function<Sentence, T>) method to the builder that lets replacing that default behaviour. Note the “T” – the client may use a different output type than String, for instance a Fact of a third-party logical inference system.

The naive (Level Six) strategy dating from before generics is simply to change the print return type to Object:

public class SentenceBuilder {
    /** This gets filled by methods subject(), does(), etc */
    Sentence sentence;
    Function<Sentence, ?> output = /* default Sentence-to-String output */;
    // ...
    public SentenceBuilder outputWith(Function<Sentence, ?> output) {
        this.output = output;
    }
    public Object print() {
        return output.apply(sentence);
    }
}

Used as:

String sentence = (String)subject(john).does(run).print();
Fact fact = (Fact)subject(john).does(run)
    .outputWith(sentenceToFact).print();

To have the builder remember the correct output type, we must use a type parameter:

public class SentenceBuilder<T> {
    /** This gets filled by methods subject(), does(), etc */
    Sentence sentence;
    Function<Sentence, T> output;
    /* WARNING - make sure all attributes are set by this constructor. */
    private SentenceBuilder(Sentence sentence, Function<Sentence, T> output) {
        // ...
    }
    
    /** The first method acts as a constructor, starts with default output type. */
    public static SentenceBuilder<String> subject(Person subject) {
        return new SentenceBuilder<>(new Sentence(subject), sentenceToString);
    }
    // ...
    public <U> SentenceBuilder<U> outputWith(Function<Sentence, U> output) {
        return new SentenceBuilder<>(this.sentence, output);
    }
    public T print() {
        return output.apply(sentence);
    }
}

Used as:

String sentence = subject(john).does(run).print();
Fact fact = subject(john).does(run)
    .outputWith(sentenceToFact).print();

This is Level One at the call-site, but Level Three in the implementation with respect to the state of the builder. If another programmer comes up and adds more state in the object, in addition to sentence and output, the outputWith method will lose information. We could keep the same instance, change ‘output’ in place and return a cast copy with the new type parameter, but other attributes refering to T will break, client code keeping a reference to the old SentenceBuilder<T> will break → Level Six, or Seven, depending on how it breaks.

A Level One solution is to encapsulate the output and the type parameter in an inner class containing only that:

public class SentenceBuilder {
    // put all state here
    Sentence sentence;
    public static SentenceBuilder.Output<String> subject(Person subject) {
        SentenceBuilder builder = new SentenceBuilder(subject);
        return builder.new Output<String>(sentenceToString);
    }
    public class Output<T> {
        Function<Sentence, T> output;
        // put all does(), on(), etc, methods here
        public <U> outputWith(Function<Sentence, U> output) {
            return new Output<>(output);
        }
        public T print() {
            return output.apply(sentence);
        }
    }
}

Balanced Method Calls

“Every from call must eventually be matched by a to invocation”

For instance, we want to write

subject(john).from(morning).to(evening).from(monday).to(friday).does(eat).a(banana)

but also

subject(john).from(monday).from(morning).to(evening).to(friday).does(eat).a(banana)

where the from-to pairs are nested.

I’ll omit the Level Six implementation, that uses a number to remember how many from have not yet been closed, check in to it does not get negative, and check in print it is zero.

A Level One fluent interface can be understood as a state machine. A state is represented by a Java Type, and a method invocation represents a transition to the type returned by that method. Requirements seen until now could be encoded as finite state machines (plus a kind of object type “register” in the case of type correlation), but now we need to record a number!

The fact that types may take parameters gives the hint on how to achieve that, in a way similar to Church encodings:

  • Encode the number zero with a type Zero

  • If the number n is represented by type T then number n+1 is represented by type Succ<T> (as in successor)

For instance, number three is represented by Succ<Succ<Succ<Zero>>>. Renaming Zero and Succ to Base and From for our case, we get something like this:

public class Base {
    public From<Base> from(Time t) {
        return new From<>(this, t);
    }
    public void print() {
        // ...
    }
}
public class From<T> { // do not inherit from Base!
    final T parent;
    // code storing 't' somewhere omitted
    public From(T parent, Time t) { this.parent = parent; }
    public From<From<T>> from(Time t) {
        return new From<>(this, t);
    }
    public T to(Time t) {
        // omitted: store 't' somewhere
        return parent;
    }
}
// example use:
base // type Base
    .from(morning) // type From<Base>
    .from(monday) // type From<From<Base>>
    .to(friday) // type From<Base>
    .to(evening) // type Base
    .print(); // returns void

// missing 'to':
base // type Base
    .from(morning) // type From<Base>
    .from(monday) // type From<From<Base>>
    .to(friday) // type From<Base>
    .print(); // error: From<Base> has no method 'print'

// extra 'to':
base // type Base
    .from(morning) // type From<Base>
    .from(monday) // type From<From<Base>>
    .to(friday) // type From<Base>
    .to(evening) // Type Base
    .to(sunset) // error: Base has no method 'to'
    .print();

Type-Correlated Balanced Methods

In continuation to the previous example, what if we allowed any Comparable range, not just Time, and further required that the to be of the same type as the input parameter?

In terms of Church encodings this means constructing a list of types. This example shows the state we should encode

from(morning) // [Time]
    .from(2) // [Time,Integer]
    .from(small) // [Time,Integer,Size]
    .to(big) // check we got a Size, then [Time,Integer]
    .to(18) // check we got an Integer, then [Time]
    .to(evening) // check we got a Time, then []

As with numbers, this may be encoded with types. An empty list is represented by Base, and a list starting with the encoded sequence H and ending with element T is represented From<H,T>. For instance, [Time,Integer,Size] is represented as From<From<From<Base,Time>,Integer>,Size>.

The implementation of the previous example may be modified as follows:

public class Base {
    public <T extends Comparable<T>> From<Base,T> from(T t) {
        return new From<>(this, t);
    }
    public void print() {
        // ...
    }
}
public class From<P,T extends Comparable<T>> { // do not inherit from Base!
    final P parent;
    // code storing 't' somewhere omitted
    public From(T parent, T t) { this.parent = parent; }
    public <U extends Comparable<U>> From<From<P,T>,U> from(U t) {
        return new From<>(this, t);
    }
    public P to(T t) {
        // omitted: store 't' somewhere
        return parent;
    }
}
// example use:
base // type Base
    .from(morning) // type From<Base, Daytime>
    .from(monday) // type From<From<Base, Daytime>, Weekday>
    .to(friday) // check friday has type Weekday, return From<Base, Daytime>
    .to(evening) // check evening has type Daytime, return Base
    .print(); // returns void

// mismatch from-to types:
base // type Base
    .from(morning) // type From<Base, Daytime>
    .from(monday) // type From<From<Base, Daytime>, Weekday>
    .to(evening) // error: Daytime can't be cast to type Weekday
    .to(friday)
    .print();

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" cssfile="">