bikeshed: Fluency

10 August 2015

Back to a simple topic this week because I'm in LA at SIGGRAPH watching cool CG videos.

A technique that I use frequently is the fluent interface. It makes for concise, readable code in certain circumstances, and has withstood a decade of use in anger. Here's an example of a fluent API in action from my Depot library:

// selects all records with age <= 25
from(PersonRecord.class).where(PersonRecord.AGE.lessEq(25)).select();

In this case, from returns a Query object which allows a variety of intermediate calls to configure the query, followed by a terminating call, in this case select, which invokes the query and returns the actual result.

Query was clearly designed with fluency in mind and provides quite a few intermediate and terminating calls:

class Query<T extends PersistentRecord> {
  // intermediate calls
  Query<T> ascending(SQLExpression<?> value)
  Query<T> cache(DepotRepository.CacheStrategy cache)
  // 30 more intermediate calls omitted for your sanity
  Query<T> where(WhereClause where)
  Query<T> whereTrue()
  // terminating calls
  int delete()
  // 19 more terminating calls omitted for the sake of the children
  List<Key<T>> selectKeys(boolean useMaster)
}

But many APIs are not designed for fluent use, even though they would benefit from it. Here's the PlayN Surface API circa 2011:

interface Surface {
  void clear();
  void drawImage(Image image, float dx, float dy);
  void drawImage(Image image, float dx, float dy, float dw, float dh);
  void drawImage(Image image, float dx, float dy, float dw, float dh, float sx, float sy, float sw, float sh);
  void drawImageCentered(Image image, float dx, float dy);
  void drawLine(float x0, float y0, float x1, float y1, float width);
  void fillRect(float x, float y, float width, float height);
  void restore();
  void rotate(float radians);
  void save();
  void scale(float sx, float sy);
  void setFillColor(int color);
  void setFillPattern(Pattern pattern);
  void setTransform(float m11, float m12, float m21, float m22, float dx, float dy);
  void transform(float m11, float m12, float m21, float m22, float dx, float dy);
  void translate(float x, float y);
}

And here's some ancient code that used that API:

SurfaceLayer bg = graphics().createSurfaceLayer(width, height);
bg.surface().setFillColor(Color.rgb(255, 255, 255));
bg.surface().fillRect(0, 0, bg.surface().width(), bg.surface().height());
bg.surface().setFillColor(Color.rgb(0, 0, 255));
bg.surface().fillRect(0, bg.surface().width() / 2, bg.surface().width(), bg.surface().height() / 2);
rootLayer.add(bg);

This API is crying out to be fluent. Compare:

SurfaceLayer bg = graphics().createSurfaceLayer(width, height);
bg.surface().setFillColor(Color.rgb(255, 255, 255)).
  fillRect(0, 0, bg.surface().width(), bg.surface().height()).
  setFillColor(Color.rgb(0, 0, 255)).
  fillRect(0, bg.surface().width() / 2, bg.surface().width(), bg.surface().height() / 2);
rootLayer.add(bg);

That's less repetitious, and the trailing dots and indentation give you a visual indication that you're continuing a chain of calls.

Fortunately for users of the PlayN library, we changed its APIs to be fluent many years back. But even today, ten years after this pattern was introduced, I frequently find myself using libraries that are not designed for fluent use and would greatly benefit from being so. This motivates me to support such a usage pattern directly in the language.

There's also the, arguably unimportant, issue that using a fluent API results in generating code that does more work than necessary, relying on the optimizer to come clean up after the abstraction to restore things back to sensibility.

The compiler doesn't necessarily know that the object reference returned by each successive call to a method is the same one it started with, so it has to dutifully stuff that back into a register and use it as the base pointer for the next method call. This is more work than just sticking the base pointer into a register up front and making a succession of calls using the same base pointer. Of course that's complicated by the fact that you're calling a method and perhaps have to save all the registers anyway, etc. etc.

Regardless, I prefer programming language constructs where abstraction and convenience can be obtained without requiring the compiler to make a big mess and then (hopefully) clean up after itself.

Dot dot ...

One idea is to provide a .. operator, which allows one to write calls like the following:

SurfaceLayer bg = graphics().createSurfaceLayer(width, height);
bg.surface().setFillColor(Color.rgb(255, 255, 255))..
  fillRect(0, 0, bg.surface().width(), bg.surface().height())..
  setFillColor(Color.rgb(0, 0, 255))..
  fillRect(0, bg.surface().width() / 2, bg.surface().width(), bg.surface().height() / 2);
rootLayer.add(bg);

The semantics of the .. operator would be to dispatch the method in question on the most recent receiver of a normal . in the current chained expression. Like many programming language features, this is tricky to define in language lawyerese, but easy to use because it's designed to "do what you mean". More examples:

// look ma, the Java standard library is fluent
List<String> strs = new ArrayList<>();
strs.add("tom")..add("dick")..add("harry");

// imagine for a moment that Guava's builders were not fluent
List<String> moreStrs = new ArrayList<>();
moreStrs.add("foo")..
  addAll(new ImmutableList.Builder<String>().add("bar")..add("baz")..build())..
  add("bippy");

You'll notice the second example nests a chain of calls inside an expression that is itself chaining calls. It seems pretty natural to have the .. operator "scoped" to a particular expression.

That raises an interesting idea though, which could further simplify the Surface example I showed above. What if the .. operator could pop out to an enclosing expression in certain (unambiguous) circumstances? For example:

SurfaceLayer bg = graphics().createSurfaceLayer(width, height);
bg.surface().setFillColor(Color.rgb(255, 255, 255))..
  fillRect(0, 0, ..width(), ..height())..
  setFillColor(Color.rgb(0, 0, 255))..
  fillRect(0, ..width() / 2, ..width(), ..height() / 2);
rootLayer.add(bg);

That's becoming hard to read, and (in spite of the fact that this example was taken straight from a real test case and in no way contrived) is perhaps not that widely useful.

Get with it

This leads me to another approach for addressing the issue: a with block. It would push another this onto the search stack for the scope of a block. Now our example would look like so:

SurfaceLayer bg = graphics().createSurfaceLayer(width, height);
with(bg.surface()) {
  setFillColor(Color.rgb(255, 255, 255));
  fillRect(0, 0, width(), height());
  setFillColor(Color.rgb(0, 0, 255));
  fillRect(0, width() / 2, width(), height() / 2);
}
rootLayer.add(bg);

Inside a with block, receiverless method calls first check the with target before popping out to search the methods accessible via the this pointer (and so on if this is a language that allows nested classes/objects, which hypothetical language certainly would).

This is nice when your chained calls are big enough to merit a separate line apiece, and neatly solves the problem of wanting to refer to the object of interest inside your chain of expressions (the nested width() and height() calls in the example).

But it doesn't have a very nice expression form. It makes sense to treat the "result" of a with block as the target object itself, so we can write:

List<String> strs = with(new ArrayList<>()) {
  add("tom");
  add("dick");
  add("harry");
};

But with blocks don't nest as elegantly as I'd like:

List<String> moreStrs = with(new ArrayList<>()) {
  add("foo");
  addAll(with(new ImmutableList.Builder<String>()) {
    add("bar");
    add("baz");
  }.build());
  add("bippy");
};

Still, the above code is not painful to look upon, so perhaps it's a tolerable inelegance given the relative infrequency with which one is likely to encounter nested with blocks.

It is nice to leverage the widespread intuition of the effect that curly braces have on scope. The fact that a nested with statement makes add mean ImmutableList.Builder.add even though we're nested inside a block where add means ArrayList.add probably didn't confuse you at all. It fits nicely with an OO programmer's mental model.

©2015 Michael Bayne