Monday, February 12, 2007

MJ!

This is my first venture into nerd blogging. I'm quite excited. Although I can't seem to find a smooth way to ease into it, so I suppose I'll just jump right in.

I've been working on an extension of Java, called MJ. Our first paper on MJ just just been accepted at ECOOP 2007. You can view the non-final version of the paper here.

MJ takes Java generics beyond genericity over the type of variables. It allows one to write code that's generic to the
structure of type variables. One can write a MJ type that "mirrors" the structure of a type variable (or a statically known type, though that is decidedly a less interesting case). For example, in MJ, I can write a generic proxy class:

class Proxy<T> {
T t;
public Proxy ( T t ) { this.t = t; }

<R,A*>[m] for(R m (A) : T.methods ) {|
R m (A a) {
return t.m(a);
}
|}
}

Proxy is a class that takes one type variable, T. Whatever T is instantiated with, Proxy<T> has the exact same methods as T, and the bodies of these methods simply forward the call to an instance of T. This "mirroring" effect is accomplished by the static-for block: a static-for block iterates over methods or fields of a type, and allows pattern-matching on the methods/fields' types and names. Type and name variables for pattern matching are declared before the static-for: in this example, type variables R and A* are declared for pattern matching on method return type and argument types, respectively, and name variable m is introduced to pattern match on the T's method names. * is a notation that we introduced to allow matching an arbitrary number of types (or names), including length 0. In this case, the method pattern provided will match method of any number of arguments, including no arguments at all. Within the static-for block then, R, A, and m can be used as regular types/names. In this example, we use these to declare methods with the exact same signature as those in T. To make this example more concrete, an instantiation of Proxy with the following class Foo:
class Foo {
int bar() { ... }
float baz() { ... }
}
Proxy<Foo> essentially has two methods, int bar(), and float baz().

The coolest thing about MJ, of course, is that it is modularly type safe -- class Proxy can be type-checked independently of the types that it may later be instantiated. Unlike other templating facilities like C++ templates, or Template Haskell, if a MJ class is type-checked, it will be type safe no matter what type parameters instantiate it.

MJ can be seen as an aspect-oriented approach. Indeed, the proxy example is often used in AOP literature. MJ can also be used to write a generic method logger class, that takes any type variable and make that a class with isomorphic method signatures as the said type variable, but with logging facilities (see paper for details). However, instead of injection of code that is often surprising to unsuspecting programmers, injection of MJ code is explicit -- a programmer must explicitly instantiate an MJ class with a type that he/she wishes to have generic code injected into. I think this is a much better approach.

One exciting directing that I want to take MJ is to give it the ability to match on annotations. Imagine this: you can write an MJ class that takes a type variable
T, and, for each field of T that is annotated with @DBMap("colname"), generate getter/setter method for that field, as well as code that retrieves/sends data to the database. What does that sound like? EJB3, perhaps? (Thanks to conversations with Martin Bravenboer and the students at Universiteit Utrecht for this example.)
Of course to make this example fully work we also need things like anti-pattern, which is in the works and I will write about later when I'm less sleepy. But essentially, if you've even tried to write annotation processing code yourself, you can probably see how much easier MJ will make your life -- it's okay, you can thank me later.