Monday, February 25, 2008

More than regular

This post discusses, whether it is possible to build more than regular languages with method chaining in Java.

Ray Myers suggested in his post Fluent Interface Grammars: Part II (Chaining), that the use of pure method chaining restricts you to regular languages. In fact this seems to be right for Java without generic types. He considered a simple non-regular language:

S ::= a B
S ::= a S B
B ::= b
The compiler should check, whether a sentence corresponds to the language or not. E.g. it should be possible to compile the following:
AbLanguage.a().a().b().b();

This should raise an error:
AbLanguage.a().a().b().b().b();

So we need at least a static method a(). It should return a type, that provides methods a() and b().
public static AbLanguage.AOpenedBase a() {
return new AOpenedBase();
}

public static class AOpenedBase {
public AOpened<AOpenedBase> a() {
return new AOpened<AOpenedBase>(this);
}

public void b() {
}
}

The implementation and return type of method b() are self explaining. It just finishes the sentence. Method a() shows how to (ab)use the generic type system to count its calls on compiler level.
public static class AOpened<T> {
public final T surroundingExpression;

public AOpened(T se) {
surroundingExpression = se;
}

public AOpened<AOpened<T>> a() {
return new AOpened<AOpened<T>>(this);
}

public T b() {
return surroundingExpression;
}
}

Every call of a() surrounds the given type with a further layer. In the opposite way b() unwraps one layer.

This technique enables us to build bracketed expressions with free depth on top of method chaining . By the way it is fairly complex to build even simple grammars and the code is not really obvious.

3 comments:

Unknown said...

Excellent work! I tried for a while to express non-regular languages using nested generics, in much the same way that you just did. I was never able to make it work.

I have to nitpick though. Your code does not implement the language I described, but rather a superset of it. For example, it accepts: "a().a().b().a().b().b();" because it correctly counts a's and b's, but doesn't strictly enforce the ordering (in this grammar, a should never appear after b).

You have indeed shown that at least some non-regular languages can be expressed using method chaining (with generics). You might be the first person to ever demonstrate this. However, the aabb puzzle is still up in the air.

Unknown said...

I just saw that you discovered the very same bug, and fixed the Scala version. I assume it's also possible to fix the Java code in the same way, but I haven't studied the Scala fix yet.

tkr said...

It is possible to fix the Java version in the same way.

The Scala code does not contain features of Scala, that do not have an equivalent in Java. It is just shorter because of some syntatic sugar like the inline attribute/constructor declaration after the class name.

Whenever returning a nested generic type the enclosing type must offer the method a() and the enclosed type (parameter type T) must not offer a().