Monday, January 7, 2008

Fluent Interface Grammars: Part I

A spectre is looming over the world of object-oriented design patterns—the spectre of fluent interfaces.

This new and unorthodox style has been espoused by such giants as Martin Fowler, such average-heighted people as Steve Asher, and such midgets as Mike Finney. Knowing my place in the order of things, I suppose it is my turn. I'll start out by reiterating some of points being made.
  • Fluent interfaces are a rockin' way to approximate the flexibility of domain specific languages (DSLs) using OO constructs.
  • Fluent interfaces are easy to use, but relatively difficult to design.
  • Fluent interfaces violate several "best practices" of interface design (such as command query separation), but are often a Good Thing nevertheless.
  • If Sun finally gives us closures in Java 7, the skys will part, the rapture will be upon us, and we will all join hands singing "Camptown Races". Also, fluent interfaces will become cooler.
As everyone keeps pointing out, a fluent interface resembles a sort of domain specific language. I would like to explore how far that analogy goes. In other words, what are the limitations on the languages that fluent interfaces can provide? Michael Feathers asks essentially the same question in The Cardinality of a Fluent Interface.

What qualities does a grammar need to have to create an embedded DSL which doesn’t allow nonsensical constructs?

Before addressing this problem, let us set some ground rules. First, we live in Java 5. Second, it is not sufficient for our interface to accept some superset of the desired language; our grammar must be an exact fit. Ungrammatical statements must not compile. They should also be underlined by our spiffy IDE :)

Note: In formal language theory, a grammar is a set of transformation rules and a language is the set of all strings that the grammar accepts. Some background with these concepts is assumed.

Fluent interfaces generally use two ways of combining tokens to form statements: Call-chaining as().seen().here(), and nesting which(works(like(so))). Some interfaces stick to one or the other, while Steve's customer creator uses both:
Customer customer = 
createCustomer.named("Bob").that(bought.item(CompactDisc).on("02/17/2006"));
He explains,

The first thing to notice is that almost half of this code is fully enclosed in parenthesis. The result of 'bought.item(CompactDisc).on("02/17/2006")' is being passed to a method named 'that'.

By contrast, this is what that statement would look like using call-chaining all the way:
Customer customer = 
createCustomer.named("Bob").that().bought().item(CompactDisc).on("02/17/2006");
And here is one of the possible translations using only nesting:
Customer customer = 
createCustomer(named("Bob",that(bought(item(CompactDisc,on("02/17/2006"))))));

The customer creator's grammar would look similar to this.
S ::= createCustomer C
C ::= $
C ::= namedSomething C
C ::= that T C
C ::= and T C
T ::= bought someItem
T ::= bought someItem onSomeDate
Examples of strings accepted by this grammar:
createCustomer
createCustomer namedSomething
createCustomer namedSomething that bought someItem
createCustomer that bought someItem and bought someItem onSomeDate
So far I have introduced the idea of formal grammars for fluent interfaces, and posed a question about their limitations. In future posts I will answer this question for each variant of fluent interfaces: call-chaining, nesting, and mixed. Have faith! I am going somewhere with this.

P.S. Steve and Mike: I was just kidding, you are both well above average height.

3 comments:

Michael Finney said...

:)

For effective readability, I wonder if a new kind of javadoc will be required for Fluent Interface APIs.

Ray said...

Mike: That's an interesting point. How do you document a method called "that()"? Docs would need to be done on the level of entire statements, not tokens.

Documenting individual tokens would be like trying to document the second semicolon in a for(;;) loop. You can't talk (meaningfully) about a piece without talking about the whole.

Of course nowadays you could just handle the situation by waiving your hands and saying "Hey world, I'm Agile! I don't document, I communicate!"

Michael Finney said...

Producing Documents from Ron Jeffries is one of the best responses to the concept of documents and agile.