My Advisor, Dr. Mayer Goldberg, claims that object-oriented programming is like functional programming, only with named arguments. Objects are closures. Constructors are lambdas. Invoking a method is applying the closure. That’s it.
I faced a good demonstrations of that claim while I was working on a small code generator. I wanted to exploit Java’s annotation processing, so I can make this generator a part of a build process. The annotation processing mechanism uses the visitor design pattern extensively. By “extensively” I actually mean “you cannot get a concrete value without using at least two visitors”. These visitors are:
- Element visitor: Since Java code is composed of packages, classes, methods and so on, you need a visitor to tell you which kind of element you are currenly holding.
- Type visitors: A type in Java can be a declared type (class, interface, etc.), an array type, primitive type and so forth, so you need a visitor to tell you which type is which.
The task that I had at hand looked simple: Given a class with methods that has a parameteric return type, get the type argument that is not the class. For example, for this class and method:
public class A { Association<A,B> getAB(); }
Return the element representing the type “B”. Simple enough, no?
That simple task required no less then 4 levels of nested visitors, shown here with some omissions. The first element is the class “A”.
tElem.accept(new SimpleElementVisitor6<Void, ...>() { public Void visitType(TypeElement e, ...) { for (Element tSubElem : e.getEnclosedElements()) { tSubElem.accept(new SimpleElementVisitor6<AssocEndSpec, ...>() { public AssocEndSpec visitExecutable(ExecutableElement ex, ...) { TypeMirror tRetTypeMirror = ex.getReturnType(); tRetTypeMirror.accept(new SimpleTypeVisitor6<TypeElement, TypeElement>() { public TypeElement visitDeclared(DeclaredType t, TypeElement enclose) { for (TypeMirror tTypeArgMirror : t.getTypeArguments()) { tTypeArgMirror.accept(new SimpleTypeVisitor6<TypeElement, ...>() { public TypeElement visitDeclared(DeclaredType t, TypeElement self) { TypeElement tArgTypeElem = (TypeElement) t.asElement(); if (!self.equals(tArgTypeElem)) { // found the little bugger! } } }, ...); } } }, ...); } }, ...); } }, ...);
That looks more like a piece of ML of Scheme code then Java! Of course I refactored it later, but it doesn’t change the simple fact that it takes 4 levels of double-dispatch just to get that little type argument. That more type-safe and well-structured then a perl hack script that would do just that, but matching the method declaration line with the following regular expression is a bit more brief:
/\w+<(\w+),\s*(\w+)>/
Btw, those of sharp sight will notice that I skipped one more visitor by using casting. I appreciate type-safety like any other programmer, but enough is enough!
