Wednesday, February 23, 2005

Coplien for Java

The justly famed Curiously Recurring Template Pattern (CRTP) is one of the cooler consequences of the design of templates in C++. But Java generics supports the same idiom:

public interface Bob<T extends Bob<T>> {
    T doWop(T t);
}

public class Fred implements Bob<Fred> {
    public Fred doWop(final Fred f);
}

Which is particularly nice if you are writing base classes.

Good for something

However, what about a case like this — lets try to rationalize my favorite pariah class, java.io.File. For starters, files and directories should be different types extending a common superclass. It makes no sense to list children on a file, nor to open an input stream on a directory. Starting simply:

public interface Entity<T extends Entity<T>> {
    void create() throws IOException;
    void delete() throws IOException;
    void renameTo(final T newLocation) throws IOException;
}

renameTo is the first interesting method. I do not want renaming files to directories and vice versa to even compile if possible, so I require the arguments to be of the same type.

Problem the first

Now a file:

public interface File<T extends Entity<T>> {
    InputStream getInputStream() throws IOException;
    OutputStream getOutputStream() throws IOException;
}

In the generic specification, why not T extends File<T>? Ah, my first chance to get bit by generics. You see, when I tried that the first time, I could then no longer implement Entity and File separately with FileImpl extending EntityImpl. Why not? Then there are two distinct superclasses, Entity<EntityImpl> and Entity<FileImpl> for my eventual concrete class tying it all together, and the compile does not like that.

(This is a great spot to point out that languages with multiple inheritance or mixins such as C++ and Scala do no suffer this limitation. It is a direct fallout of single inheritance with generics. You should try it yourself a few times to convince yourself; it took me a while to accept the problem at face value.)

And a directory:

public interface Directory<T extends Entity<T>> {
    boolean isRoot() throws IOException;
}

(By now you are probably wondering about throwing IOException from everywhere. My practical use for reimplementing java.io.File includes networked files so I must accept that all operations potentially fail. Throwing is so much better than the broken example of java.io.File, some methods returning false on failure. And just look at the horror that is createNewFile(), both throwing IOException and returning false depending on the exact failure.)

Problem the second

What about some implementations?

public abstract class LocalEntity <T extends LocalEntity<T>>
        implements Entity<T>, Comparable<LocalEntity<T>> {
    protected final File backing;

    // Constructor, implementations
}

And yet another gotcha! See that backing is marked protected? This is not of necessity because I access backing from classes which extend LocalEntity. It is required because I access backing from within LocalEntity. Here:

public void delete()
        throws IOException {
    if (!backing.delete())
        throw new IOException();
}

public void renameTo(final T newLocation)
        throws IOException {
    if (!backing.rename(newLocation.backing))
        throw new IOException();
}

The delete() method compiles fine if I change backing from protected to private. The renameTo(T) method, however, does not: specifically, newLocation.backing is no longer accessible. That is because T is not the same type as myself; the generic declaration declares that it is some type which extends myself. Hence, I need protected access. That one took a while for me to grok as well.

The final problem

One last final trick. What about the final concrete classes? I want to be able to write Directory dir = new LocalDirectory("/home/binkley") and not worry about generics. There takes quite a bit more magic for that to happen. Watch:

public abstract class LocalDirectoryType <T extends LocalDirectoryType<T>>
        extends LocalEntity<T> implements Directory<T> {
    // Constructors forwarding to super; no methods or members
}

public class LocalDirectory
        extends LocalDirectoryType<LocalDirectory> {
    // Constructors, and unimplemented directory-specific methods
}

Wow! The chicanery is necessary so that the end product, LocalDirectory has no generics. The implementor should do all the work, not the user. I make one last use of CRTP, extend an implementation superclass and add on the Directory interface.

Conclusion

That is a lot of typing. I hope it is all worth it. In C++-land this sort of thing is considered high sport, but most Java folks I know get a sour expression at the sight of generics. Suum cuique pulchrum est.

If you would like to see a fuller example, please drop me a line.

UPDATE: I add an important correction in the following post.

No comments: