Combinator-style API

26.08.2013 Permalink

Over the last few months I unconsiously applied a new style of API design to programs I wrote. I guess I adopted it because I've read quite a lot of functional code and papers that uses this 'combinator style'.

But only today, while discussing an API with a colleague, I became fully aware that this style may seem unfamiliar to most Java people. I can illustrate the situation with some snippets of Java code.

For a customer we had to implement a data validation library. An important requirement was that the constraints can be selected very individually by runtime configuration and that it must be very easy to add or remove single constraints. JSR-303 annotations were no good fit because the rules are statically attached via annotations to class getters, so we had to find a more flexible approach on our own.

A constraint is basically only a function, so we started with an interface similar to this:
  public interface Constraint {
    String check (Object o);
  }
Now, you can implement a dozen of simple constraints like NotNull, NotBlank, GreaterThan, Matches etc, which all work on scalar values. If the class implementing Constraint needs a parameter (like Matches would need a regular expression) you can pass it to its constructor.

The normal use case is to apply a validator to a complex object, let's assume an Address containing street, zipcode and city. So let's introduce a Validator class...

At this point, one of the rules that I learned to value very highly, kicked in: Minimize the number of concepts! Because each concept must be understood by itself and may interact with others, it potentially raises complexity and cost far beyond its benefit.

We decided that a validator is only a Constraint that works on an object graph and applies other constraints to do the actual validation, so the Validator class implements Constraint. (Compare that to javax.validation API: Constraints are -- from an API standpoint -- only annotations and there is no conceptual link between such an annotation and the Validator interface.)

But our Validator cannot simply take Constraint instances, it must know which Constraint must be applied to which bean property. We needed something that is able to get the property value and then apply the Constraint. This gave birth to another subtype of Constraint, that we called Property. An instance of Property takes a bean property name and a Constraint and applies the Constraint to the value it retrieved via reflection.

Because Property is a Constraint we can directly pass it to a Validator. With some additional factory functions the resulting API can be used like so:
Validator addressValidator = new Validator(
	property("street", stringLength(1, 50)),
	property("zipcode", regEx("[0-9]{5}")),
	property("city", notNull()));
I hope that you can see the principle here: we define and encapsulate combinable pieces of functionality. Looking from the outside, we only introduced pieces of code that validate data. Due to usage of the Constraint interface as parameter as well as contract the pieces are composable, although underneath they act very differently.

That style of API design is in fact around for a long time: It seems that there are two flavours in practice: there are combinators that compose functionality (like parsers or validators) and those that step-wise transform data (like sequences or graphical elements). The essence is the same: functions yield something that is compatible with what other members of the same 'family' of functions expect as input.

And as a nice goody: if the combinators are named with great care then programs that are based on those read much like a domain specific language.