Friday, November 27, 2009

Extension methods: the case for language-level delegates in Java

Extension methods

In There's not a moment to lose!, Mark Reinhold announces something completely different: Java 7 will have closures after all. Huzzah! And in that announcement, he lists enabling language changes for them:

  • Literal syntax
  • Function types
  • Closure conversion
  • Extension methods

I want to look at extension methods closely. Alex Miller does an admirable task of summarizing JDK7 changes; that's a good place to start. And some code:

public final class Filtering {
    public static <T, C extends Collection<T>> C filterInto(
            Collection<T> input, C output, Filter<T> filter) {
        for (T item : input)
            if (filter.keep(item))
                output.add(item);

        return output;
    }

    interface Filter<T> {
        boolean keep(T element);
    }
}

Given extension methods, a coder can write:

// static imports
Collection<String> names = asList("Bob", "Nancy", "Ted");
List<String> bobNames = names.filterInto(
        new ArrayList<>(), // new diamond syntax
        #(String name) "Bob".equals(name)); // new closure syntax

And some magic means will find Filtering#filterInto(Collection, Collection, Filtering.Filter) and transform the instance method call on Collection into a static method call on Filtering. Rules for finding Filterable vary. (Did you read Alex Miller's link?)

Here is an alternative.

Delegation

I am a fan of delegates and have writen about them often: I'd like them in Java. With delegates, the magic for finding #filterInto is less magical, and more under the control of the coder, but just as clean to use:

public final class FilteringCollection<T>
        implements Collection<T>, Filtering {
    // Some literate programming sugar for the static importers
    public static <T> FilteringCollection<T> filtering(
            Collection<T> input) {
        return new FilteringCollection(input);
    }

    public FilteringCollection(final Collection<T> input) {
        this = input; // the delegation proposal
    }

    public <C extends Collection<T>> C filterInto(
            C output, Filter<T> filter) {
        for (T item : this)
            if (filter.keep(item))
                output.add(item);

        return output;
    }
}

Usage now looks like:

// static imports
FilteringCollection<String> names = filtering(asList("Bob", "Nancy", "Ted"));
List<String> bobNames = names.filterInto(
        new ArrayList<>(), // new diamond syntax
        #(String name) "Bob".equals(name)); // new closure syntax

What's the difference?

Key here is that for the coder the only difference is delegation uses a static factory method to wrap input. This is not a drawback: it is an advantage. Less magic makes Java programs easier to maintain. There is no loss here in power.

Why bother with the delegation proposal?

Would you like to write the boilerplate forwarding methods of Collection? I wouldn't! It gets even worse as you move down the inheritance tree for Collection.

Postscript

How would you move down Collection's inheritance tree any how?

Beyond any current proposals, some javac author could implement type erasure for base classes (very carefully) in context of delegation:

public final class FilteringCollection<T, D extends Collection<T>>
        implements D, Filtering {
    // Only factory/constructor changes
    public static <T, D extends Collection<T>> FilteringCollection<T>
            filtering(D input) {
        return new FilteringCollection(input);
    }

    public FilteringCollection(D input) {
        this = input;
    }
}

Because the coder need not implement the dozens of forwarding methods, neither does he need to know what they are! The compiler sets the type of D to the precise collection used in the factory/constructor, and the resulting object provides all public methods.

But this is well beyond the scope of delegation. Go code Scala, or something.

UPDATE: Food for thought on extension methods from RĂ©mi Forax.

UPDATE #2: This just gets more interesting: in Extension methods vs. method currying, Stefan Schulz discusses the alternative of currying.

No comments: