Tuesday, 27 November 2007

Java 7 - Extension methods

A recent document revealed some possible language changes that are being proposed. One of these is is extension methods.

Extension methods

Extension methods allow a user to 'add' a method to an interface or class that you don't control. The original document is linked with BGGA closures. And was followed up by Peter Ahe

The classic example of the proposal is as follows (known as use-site extension methods):

// in the application
import static java.util.Collections.sort;
List list = ...
list.sort();

Thus, the sort method appears to be a method on the list, even though we haven't actually changed the List interface. When it is compiled, the extension method is removed, and replaced with the static method call.

Peter Ahe pointed out some flaws with this design. He also proposes declaration-site extension methods:

// in the JDK
public interface List {
  void sort() import static java.util.Collections.sort;
  ...
}

// in the application
list.sort();

In this case, it is easier for the user to find the implementation of the sort method, as it genuinely is a method on the interface, just one implemented elsewhere. Unfortunately, the downside is that now only the JDK authors can add methods to the List interface in this way.

My proposal is that the freedom given to the user by the use-site approach is far more desirable, but the possible side-effects are nasty. My preference would be to add a visible marker to show that this isn't a regular method:

// in the application
import static java.util.Collections.sort;
List list = ...
list.do.sort();

Note the '.do.'. This provides the user with the clear readability to show that something 'out of the ordinary' is going on.

In addition, I would argue that the static method should be marked to indicate that it can be used as an extension method, probably using an annotation:

// in the JDK
public class Collections {
  @ExtensionMethod
  public void sort(List list) { ...}
}

This provides the final piece of the puzzle, preventing innapropriate methods from being used as extension methods. While it does suffer from the same issue of pushing control back to the library author (the JDK), it avoids the issue of which methods get added to the List interface.

The point is that as many methods as appropriate can be tagged with @ExtensionMethod. There is no conceptual overhead in doing so, and it doesn't expand the conceptual weight or complexity of the ListInterface itself.

Summary

I've outlined an alternative proposal for extension methods, that tries to focus on freedom for users, without compromising readability or allowing confusing options.

Opinions welcome, as always :-)

31 comments:

  1. The Groovy way [1] leaves it up to the user without requiring the author to do something (which, if we leave it up to Sun, they'll forget the annotation on half of the methods that we want, like String.format).

    [1] http://groovy.codehaus.org/Pimp+my+Library+Pattern

    ReplyDelete
  2. Yay, extension methods would be great to have!
    Not sure how I like the "do" marker though, its the same problem I have with Bloch's "do" version of the C# using statement, it reuses an established keyword with very different semantics - I thought we'd learned not to do this from C++ (and from explaining the 3 semantics behind "final" to newies).

    IDE's could simply flag out extension methods just like they mark member wide variables no?

    ReplyDelete
  3. If I understand the requirement completely, I'd say you should have a look at how this is done in C# 3.0. There, you define extension methods like this:

    public static class Extensions {
    public static string HelloWorld(this int i) {
    return "Hello world";
    }
    }

    class Application {
    static void Main(string[] args) {
    int i = 1;
    Console.WriteLine(i.HelloWorld());
    }
    }

    E.g., the first argument to the extension method has to be the magic 'this' keyword, followed by the type you want to extend.

    ReplyDelete
  4. I agree that you shouldn't have to write anything back into the interface, but I also dislike the .do. notation and not having a declarative notation.

    Instead I would suggest that the import/mix-in/merge is more declarative before usage, such as (e.g):

    List list = new ArrayList();
    list imports java.util.Collections.sort; (or another keyword)
    list.sort();

    I assume that for a method to become an extension method, the first argument to the method must match the type of the extendee, such that:

    public static void sort(List l)
    becomes
    list.sort()

    and

    public static void sort(List l, Comparator c)
    becomes
    list.sort(aComparator)

    This basically matches the byte code anyhow for a class method.

    If this is the case, you would not necessarily have to annotate possible extension methods, but only allow static methods with matching type. Of course it would be better with declarative annotation, but you can have the other one in (and then throw a compiler warning).

    Just my 10p.

    ReplyDelete
  5. I haven't looked at the implementation details or the syntax yet, but the concept really grabs me... awesome stuff.

    Although static imports have mostly tidied up the code for this sort of situation, it's still "the wrong way round" and doesn't feel object oriented. I'd certainly prefer to be able to write collection.sort() than sort(collection).

    ReplyDelete
  6. It's a little like adding multiple dispatch to java. Adding sth. to the interface is a no-no. I see problems with conflicting, hiding and overriding methods this way. What if java.util.list would already have declared a method "sort"?
    I'll stick to real multiple dispatch like in CLOS.

    ReplyDelete
  7. Another option would be:

    interface MyCollection extends Collection {
    @StaticExternalMethod(from=Collections.class)
    Object max(@Self Collection c, Comparator comp)
    }

    Behind the scenes, the compiler would generate a delegating proxy which would automatically wrap the Collection instances when they are assigned to variable of this type (similar to the autoboxing mechanism).

    ReplyDelete
  8. Stephen Colebourne27 November 2007 at 15:00

    @Noah, Unfortunately, leaving this entirely up to the user means that inappropriate methods can be called. Not every static method is intended to be used like this.

    @Casper, IDE marking is insufficient, see Peter Ahe's blog. One alternative to .do. would be ->, but the key point is making it clear that this isn't a normal method call.

    @Asbjorn, the 'this' here looks very similar in concept to the @ExtensionMethod.

    @Niels, defining the import on the variable would be overkill IMO. By that stage, you might as well just use the current static method calling style.

    @Philipp, one of the reasons for .do. is to avoid the clash of a sort() method on the interface with the sort() extension method.

    @Dimitar, you are proposing definition-site extension methods, as per Peter Ahe. These don't give enough freedom to the end user IMO.

    ReplyDelete
  9. What is the scope of this change? Is it adding a method to one particular instance of a class, or does it effect all instances of the class created after the extension method has been added? If it's global I can't see how one would manage multiple objects or even multiple threads all competing to inject their own version of the sort method. If it's not global I don't see why it's necessary on any interface or non-final class because you can inherit and implement anything you want. Any final class is final because the author has decided to not let you extend it. So extension methods do not make since there either. What am I missing? What does this make possible that was not possible before?

    ReplyDelete
  10. It would be better if JVM (and javac) allowed non-abstract interface methods.

    Interface fields will not have fields, so there are no big problems with multiple inheritance.

    ReplyDelete
  11. Stephen Colebourne27 November 2007 at 17:48

    @Aberrant, This is mainly about allowing developers to 'add' methods to interfaces/classes written by others. For example, the List interface is written by Sun, and can't be changed by the average developer (in fact it can't be changed by anyone due to backwards compatibility).

    If you want to sort a list, you currently have to use a static method on collections - Collections.sort(List). This change allows you to write this in a more OO style - list.do.sort();

    Extension methods as described here do not affect all instances of the class, just those in a single source file where the import occurs.

    ReplyDelete
  12. I am not convinced this is the appropriate use of annotations in this context. Annotations are "meta-data", extra information and "extension" methods are just static methods that the compiler magically makes them look like methods on the class.

    Honestly, I don't think extension methods are a "good thing". It destroys readability IMO and by design it masks that it is a static method in another class.

    Instead of just adding every new language feature from C# 3.0 we must evaluate it's usefulness.

    ReplyDelete
  13. Well I guess then I really just don't get it, sorry. If I want a list that sorts.

    ListWithSort list = new ListWithSort();
    list.sort();

    The implementation of ListWithSort is not significantly more code then your proposal for extensions and it's OO and it's very clear where the code comes from.

    *shrug* sorry.

    ReplyDelete
  14. First of all, I think using sort as an example for an extension method is misleading... Furthermore, if I have to do list.do.sort(), then it does not bring any benefit to me compared to sort(list). I think the major benefit of an extension method is transparency, which means that the user does not have to know that it is an extension method. If we remove the transparency, then I fail to see any benefit of having extension methods at all. I really suggest you take a look at how it is done in C#.

    ReplyDelete
  15. I agree with vhi, you either make it look like a normal method or there just isn't any advantage IMHO.

    ReplyDelete
  16. Stephan,

    Cool link.Yes i agree with your DSL oriented proposal and hope it may get implemented .

    Thanks
    Prashant

    ReplyDelete
  17. Extension methods are great.
    Instead of allowing only set of methods(which are marked with annotation), I believe every method should be extenddable.

    ReplyDelete
  18. Why you always reject discussing design issues and propose workarounds to fix flows in architecture? And those workaround typically lead to even worse flows. What happend with OO design and programming? What about maintenance cost of those extended methods? What if you have library A which introduced a "sort" extension and you have library B which relies on its own "sort" extension? How would you manage that?

    ReplyDelete
  19. The advantage of extension methods isn't about list.sort() vs sort(list), it's about

    transform(filter(filter(list, Somthing.class), aPredicate), aTransformation)

    vs.

    list.filter(Something.class).filter(aPredicate).transform(aTransformation)

    [ Using google collections Iterables.* methods ]

    The first takes several seconds of looking at it to work out which methods and which arguments go together. The second makes it much clearer.

    ReplyDelete
  20. To Ben Lings: list.filter(Something.class).filter(aPredicate).transform(aTransformation) also makes you belive that List interface implements/has the "filter" method. So one may enjoy in this specific context. Then this happy person moves to another project and oops this method is not there or perhaps does something else as in context of the projext someone "extended" it like he wanted.

    I can understand/accept those extension methods but I think it is matter of design/programming culture. From my point of view it is a hack in OO design/programming.

    ReplyDelete
  21. Adding marginally useful features that appeal to a small number of "language geek" developers vs. fixing fundamental Java problems that impact most users/customers is a clear sign that Java is getting ready to jump the shark. Maybe it already has.

    ReplyDelete
  22. Extension methods are useful in a lot of cases to add more readability. It is a so much necessary feature to adopt other features - as closures - smoothly. As someone says before here, because of compatibility, none will change List interface.

    Think about write code like this:
    Integer ten = 10;
    Date later = now.add(ten.minutes());

    This is a huge improve readability (and you could do it for any library), so, don't be afraid of extension methods.

    ReplyDelete
  23. To Marcos: With modern IDEs readability is a very personal issue. I have not had any problems with that since I started with Java 8 years ago. It took some time to accept the philosophy of the language and OO programming rules and techniques. And this is much more important then readability. Especially those examples I see here are not convincing. I can also say that the code with extension methods is not readable enough. Perhaps I prefer something like:

    Date later = ten(minutes) from now

    So what? Which example is more readable? Why yours is better then mine? And do not tell me about implementation. One can always invent "extend-anything-in-java" feature. I am quit sure I have seen examples...

    ReplyDelete
  24. yury:

    if "ten" is a method, you will do it for any integer? Of course not, but with Extension methods, your example (or something nearly of it) become possible:

    Integer ten = 10;
    Date later = ten.minutes().from().now();

    I even implemented something like that using a fluent interface:

    Date later = $(10).minutes().from().now();

    Moreover, readability is not an IDE issue but it is a code issue and is not on the other side of OO programming rules and techniques, but provided by them. Code is not more readable because of extension method such as code is not more readable because of "if" (which abuse could leads to high cyclomatic complexity), "switch" (fall through) and a lot of other language features. Any feature could be used in a wrong way, and could be used in a right way.

    Kind Regards

    ReplyDelete
  25. Stephen Colebourne1 December 2007 at 01:18

    Thanks for the comments - Ben's exmaple is particularly good

    transform(filter(filter(list, Somthing.class), aPredicate), aTransformation)

    vs.

    list.filter(Something.class).filter(aPredicate).transform(aTransformation)

    However, I want to re-iterate, that I am proposing:

    list.do.filter(Something.class).do.filter(aPredicate).do.transform(aTransformation)

    because it makes it clear that the extension method is not actually on the class, but is statically imported.

    ReplyDelete
  26. Krzysztof Kowalczyk1 December 2007 at 19:35

    I would like to have extension possibility for any existing static function like:

    public class Foo{
    public static int foo(Type x,int y){...}
    }

    And then I should decide if I want to use it as extension (like import static):


    import foo.Type;
    extension Foo.foo; // first parameter will be extended

    or maybe:
    extension Foo.foo(extend Type,int); // one choose witch parameter to extend

    or readable:
    extend Type with Foo.foo;

    than I can use :
    Type type = new Type();
    y = type.foo(1);

    Pros:
    - you can decide what you can use as extension, and how (2nd approach)
    - extension is explicit
    - no changes in existing code
    - you don't need "do", or "->" any more
    - you can use "extend MyDsl.*;" and provide full dsl for your code! (only in first approach)
    - can be used in automatic cleanups :)

    ReplyDelete
  27. Peter Ahe has suggested extending the interfaces instead of ad hoc extensions (http://digital-sushi.org/entry/declaration-site-extension-methods). My preferred alternative is to turn interfaces into traits, e.g.:

    interface X { int foo() { return 1; } }

    interface Y { int foo() { return 2; } }

    class XY implements X, Y {
    public int foo() { // must write a foo to resolve ambiguity
    return X.foo(); // diambiguate
    }
    }

    ReplyDelete
  28. I don't see the point in this entire exercise. It misdirects the user from where the sort actually resides and introduces more syntax for developers to understand.

    So what that not everything you might wish to do on a list is a method of list. That's real life. Inventing ways to make believe everything is just makes it harder to understand the reality.

    ReplyDelete
  29. I have right now here a use case where I'd like to have transparent client side extension methods, so I could do some kind of duck typing (by simply switching a single generic type declaration); I have to apply program transformations for achieving this at the moment.

    Ultimately what I want would be a union type and transparent client side extension methods so I could write sth like:

    void find(List|Sortable sortMe){ ...
    sortMe.sort();
    ...}

    instead of doing everywhere

    void find(Object sortMe){ ...
    if(sortMe instanceof List){
    Collections.sort(sortMe);
    }else if(sortMe instanceof Sortable){
    ((Sortable)sortMe).sort();
    }else throw...
    ...}

    that wouldn't work with the .do. syntax :-(

    so should we also discuss union types?

    ReplyDelete
  30. I posted the following on Peter Ahe Blog, it references this Blog - hence the cross posting:

    Re. use-site extensions, Josh Bloch is of course right in saying that the trait/mixin/declaration-site aren't as flexible. But I am not totally convinced about the extension mechanism as proposed - maybe I need to know more about it. My particular concerns are:

    1. They look like they do dynamic dispatch, but they don't.
    2. They have a limited use case - statically imported static methods

    Perhaps -> could be used and that this notation is for the first argument of *any* method regardless of how its name is made available and this first argument *includes* the hidden this of instance methods. Therefore you could write:

    list -> filter( test1 ) -> filter( test2 ); // static void filter(List, Predicate) is statically imported

    list1 -> addAll( list 2 ) -> addAll( list3 ); // addAll(List) is an instance method in List

    The above notation is similar to the builder notation, also proposed for Java 7 (new X().setProp1().setProp2() where the props return void) and is meant to combine the two proposals. The same as the builder proposal; the value of the second statement above is list1.addAll( list3 ). Different than the builder proposal; the intermediate values, e.g. list1.addAll( list2 ), are always discarded.

    Stephen Colebourne has suggested something similar but proposed .do. instead of -> and didn't include the original argument getting passed to all the methods, i.e in the second example above list1 is given to both addAlls in this proposal were as in Stephen's the second addAll receives the value from the first.

    ReplyDelete
  31. Oh god no. Please no. There is NOTHING wrong with sort(list). Java is turning into C++! I LIKE it when I can look at a line of code, and tell EXACTLY what's going on.

    ReplyDelete