Sunday, December 9, 2007

Extension methods proposal

I'd like to talk here about another Java 7 feature proposal lobbied by Google that generated lots of waves in the blog-sphere after being revealed by Neal Gafter. A lot have been said (Peter, Alex, Stephen, Rémi and Stefan) so I'll add just 2 humble observations.

Adding capabilities to the language vs. maintaining a safe and clear programming environment is a tough balance to keep. I had my doubts in this particular case, and I think I tend to favor it, with one modification. I think it would be better if extension method import was different from normal static import, so the developer knows exactly what he is doing. I propose following syntax (that BTW does not require any additional keywords in Java)

import static java.util.Collections.sort
extends java.util.List;
Compiler would then check that the first argument of sort is a List (or its super-class or super-interface) and compile
List list = ...;
list.sort();

into
Collections.sort(list);
The main advantage of this proposal is that extension methods are imported consciously, rather than implicitly.

The "importer" is also given some control over the overloading rules for the extension method - what if Collection has a sort method? what if ArrayList does? well, with the syntax I propose here the developer targets the method to a specific class, so overloading rules can be applied just as if the method was in that class, in this example it would overload Collection one, but not ArrayList one. It reminds me of the Ruby feature that Neal Ford had mentioned - calling a method, and if it does not exist, executing some code piece instead. So with proper overloading rules, could imported extension method be Java alternative for method missing? At last, there's this dark corner question "What if List had a sort method?" Personally, I think that if the class explicitly imports an extension method, it should be treated as local definition, while the method defined in List as inherited definition, and therefore (following the principles laid down by Gilad Bracha in one of his recent papers) the extension method should override the one in List. This is somewhat similar to the infamous "Mother May I" rule, but I better leave this for greater minds to solve.

Another interesting aspect of extension methods in general is their behavior in case the first parameter of the method is generic. Take Collections.max method for example. It accepts collections which element type extends <T extends Object & Comparable<? super T>>. AFAIK there is no definition of extension method behavior with generics. Maybe they are not allowed, but if they were, I would expect that list.max() compiles only if elements of my list are compatible with the static method definition, which in this particular case means that they should be Comparable to each other. Now this is an enhancement of Java generics system - being able to add methods to classes based on their generic parameters! Wow, it's somewhat spooky, but really powerful, I can imagine a few neat things I could do with this :-) and since I am now going to spend some time dreaming, I am going to leave you to think about this...

P.S. (12/12/07) Just came across a vivid discussion of similar feature in .NET. First reaction - "ah, oh, I get it now, it's another one of those .NET has it so we gotta have it too features!". But seriously now - let's learn from their experience, the conclusions are pretty similar to what is discussed here.

3 comments:

hlovatt said...

Whilst your syntax is better than others proposed I am not a fan of any of the syntaxes - they looks like a dynamic dispatch call but aren't and they have a narrow use case (statically imported static methods). To see the dynamic dispatch problem consider:

interface X {}
interface Y {}
class XY implements XY {}

...

import static XExtension; // XExtension defines m
import static YExtension; // YExtension also defines m

X xy1 = new XY();
Y xy2 = new XY();
XY xy3 = new XY();
xy1.m(); // calls X.m
xy2.m(); // calls Y.m
xy3.m(); // Error

Very confusing! They are all XYs after all! Not at all OO!

The example would of cause be a little better with your proposed syntax, but not much.

An alternative might be a with statement (like Pascal). You could use a with keyword or the symbol ->. For example suppose filter was a statically imported method that worked on a list:

list -> filter(test);

The idea of the -> is that it replaces the first argument of a call, including the hidden this. It also can be applied to a block. AddAll is a method in List already, so the following:

list -> { addAll(list2); addAll(list3); };

Puts all the lists into list. The -> operator would return as its value the result of the last operation. This would allow it to be used in a builder pattern. EG

House h = new Building() -> { add(doors); add(windows); makeHouse(); };

Re the builder pattern, there is another Java 7 proposal for this.

The with clause idea outlined above is similar to Stephen Colebourne's .do. proposal but additionally allows application to a block. Many syntax variations would be possible, e.g. with keyword, a different symbol. But I think the semantics of a with block that returns a value would be a useful addition.

Yardena said...

Hi Howard,

Thanks for commenting on the post, I must admit that I share your concerns to some extent. But it's not really a new problem. We already have ambiguity with constants with the same name inherited from different interfaces, when referenced via or within the instance. With Rémi's proposal it would happen to methods too - if I had a static method with same name and signature in 2 different interfaces I implement.

I think that "static" stuff in Java is generally anti-OO, and it brings this smell into any usage. However, from a practical perspective I hope it's a corner case, and in most cases benefit would outweigh the potential ambiguity. And IDEs for example could highlight such invocations of "pseudo-instance but really static" method.

Your proposal is interesting (oh sweet memories of Pascal), I find myself creating fluent interfaces to achieve similar thing. I am also following Stephen's work. For now the syntax looks somewhat alien to Java IMO, but maybe it's matter of getting used to.

Yardena.

hlovatt said...

I have blogged about an alternative, with clauses:

http://www.artima.com/weblogs/viewpost.jsp?thread=220783

Typical usages are:

list -> synchronizedList() -> sort();
list -> { add( 1, "A" ); add( 2, "B" ); };
Home home = new Builder() -> { setWindows( windows ); setDoors( doors ); makeHome(); };