The Source for Java Technology Collaboration
User: Password:



Rémi Forax

Rémi Forax's Blog

Java 7 - Extension methods

Posted by forax on November 29, 2007 at 03:15 AM | Comments (12)

Recently, Neals, Peter and Stephen blog about extension methods.

Why i hate (yes hate) use-site extension ?

What i love (yes love) with Java is the fact that i can take a look to a screen over one of my student shoulder and be able to say is the snippet i see is correct or not.
With the use-site extension proposed by Neal, the behavior of a code depends on some static imports that are located in the beginning of the file. So you can't understand a code without browsing the entire file.

Demonstration. Do you understand this code ?

 ...
 String text=...
 for(int i=0;i<text.size();i++) {
   System.out.println(text.charAt(i));
 }

Hum, i have forget to say that the begining of the file contains that declaration: import static Strings. and that the class Strings is defined like that:

 public class Strings {
   public static int size(String s) {
     return s.trim().length();
   }
 }

I'am sure i will hate that.
It will remember me the the mess of C++ member functions: "where is the f***ing def of this method".

Furthermore, import static has already some limitations, the code below doesn't compile. This case is currently rare but with extension methods it will be more frequent.

  import static java.util.Arrays.*;

  public class StaticImport {
    public static void main(String[] args) {
      toString(args);
    }
  }

Why i prefer declaration-site extension methods ?

It's definitely a good property of a language to be able to understand a code that deals with some java.util.Lists, just by knowing the declaration of this type. If i don't know how to use it, i can just to browse its code. After that, because i have a good brain storage device, i will be able to understand ALL codes that deals with java.util.List.

Another proposed syntax

I think there is some problems with the syntax proposed by Peter.

  package java.util;
  public interface List<E> … {
  …
    void sort() import static java.util.Collections.sort;
  …
  }
 
  1. A new syntax, again !
  2. The syntax let a developer think that this method can be overriden but without a modified VM it's not possible.
  3. The constraint on the elements of the list (must be comparable) are not visible.

Here is my proposed syntax, allow to declare static method in interfaces (in the same time, we close one the top 25 RFE Bug 4093687) and add some sugar in the compiler.

  package java.util;
  public interface List<E> … {
  …
    @ExtensionMethod
    static <T extends Comparable<? super T>> void sort(List<T> list) {
      java.util.Collections.sort(list);
    }
  …
  }

Like other proposed syntax, the compiler is modified to transform an instance call to a static call. The following code

  List<String> list=...
  list.sort();
 
is rewritten to
  List<String> list=...
  List.sort(list);
 

@ExtensionMethod (borrowed from Stephen blog) like @Override is not mandatory and tells the compiler to verify that the first parameter of the static method is the same type that the declaring interface.

I wait your comments,
Rémi


Bookmark blog post: del.icio.us del.icio.us Digg Digg DZone DZone Furl Furl Reddit Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • I suppose it depends on your screen size. Maybe with a tall screen you could see the imported extension methods. Your proposed syntax is not easy to read (you can understand it if you look hard) and might not even fit on the screen horizontally... There are too many different symbols, inheritance, and the like.

    I'm not keen on extension methods in Java (okay for scripting languages, as you know what you're getting into) mainly because I don't like the potential confusion for an imported extension method "hiding" a declared method with the same name. Or the opposite? Difficult to remember. Again, resolvable, but reducing code clarity.

    Your first criticism seems to be the ambiguity caused by not being able to see the relevant import statement. How is that different from confusion caused when importing java.util.* and java.sql.* and referring to Date? Or java.util.* and java.awt.* and List? I don't see you recommending use of fully-qualified names and nothing else, so the line between clarity and concise code seems to be subjective.

    In the same way, I was originally for a real property syntax in Java 7, as opposed to the "get", "set", and "is" convention, similar to C#: concise declaration, concise use, and (for no extra cost) a clear differentiation of a property and some arbitrary "get" method. Current property proposals go way beyond that, with all sorts of syntax (instead of just ".") to do some advanced stuff with another set of concepts and syntax (the opposite of simplification). If you really want a much more powerful language, instead of aiming for clarity-enhancing features, consider working on a scripting language. For that matter, Groovy has almost all of what people seem to be asking for...

    - Chris

    Posted by: chris_e_brown on November 29, 2007 at 03:50 AM

  • Use-site extension methods allow people in the Java community at large to create useful, targeted APIs for interfaces which are designed by someone else, and which de facto can't be changed. The List API was not designed by you or me and cannot be changed by us. Use-site extension methods allow us to "extend" the API in ways that make our intentions more clear. Declaration-site extension methods allow the designer of an API some flexibility in extending their interfaces after an earlier version is already in widespread distribution. I think both use cases are valuable.

    If you're talking about List, some people believe list.first() is more clear than list.get(0) and list.last() is more clear than list.get(list.size() - 1). There was a long discussion about this re: Ruby versus Java APIs some months back, under the heading IIRC "humane APIs".

    As long as there are clear rules for what code gets invoked in the presence of static extension method imports, then I don't see a problem. When working with any class, for methods defined in some superclass I don't immediately know where that method is defined, overridden, or what it does, exactly. My IDE or other tools help me figure that out. What's important is that method selection is rule-based, statically constrained, and well-understood.

    And for the record, I know exactly what aString.size() means :).

    Regards
    Patrick

    Posted by: pdoubleya on November 29, 2007 at 04:12 AM

  • I like the syntax with static interface methods...

    Posted by: zero on November 29, 2007 at 04:42 AM

  • As others have mentioned user-site extension methods provide the library user with the ability to enrich the interface based on the current contract without requiring a new version from the library provider. As to the static import issue, I think IDEs could help here, in the same vein as the ability to recognize fields with syntax highlighting rather than writing 'this.' when referencing fields.

    Posted by: khalilb on November 29, 2007 at 06:55 AM

  • I understand why you have a problem with the Neal's extension methods, I hadn't looked at it like that. The problem of course being that the import statement does not say WHICH classes it affects! So if you have some code with a lot of those static imports you would have to check ALL of them to figure out where that strange new method comes from.

    But for the same reason as given by others before I don't like declaration-site ext.methods because it does not give ME, as a programmer, any "power".

    I wonder if your problem with use-site ext.methods could not be lessened by adding a more explicit definition, something that will show which classes will be affected by the import? Something like:

    import static org.test.utils.Strings.* for String;

    Posted by: quintesse on November 29, 2007 at 08:01 AM

  • I'm not sure I like this. It's just not clear enough where the method is coming from. It also has a rather large scope, it effects everything defined in the entire file. How about a variation on casting so it oblivious that you no longer have a standard list.

    List list=...
    ([ListExt]list).sort();
    One might wish to keep the extended interface around.
    ListExt exList = [ListExt]list;
    exList.sort();
    exList.print();
    exList.shuffle();
    exList.print();
    One might define the ListExt class as such:
    public interface ListExt static extends List{
    ...
    @ExtensionMethod
    static <T extends Comparable<? super T>> void sort(List<T> list) {
    java.util.Collections.sort(list);
    }
    ...
    }

    The "static extends" would mean I have extension methods to add to this interface. Then anyone could implement there own extensions, to anything and apply them to objects who's creation they do not control . Also its very explicit about whats going on. What do you think?

    Posted by: aberrant on November 29, 2007 at 10:23 AM

  • Nicely put why I hate this extension proposal as well. You can't even trust reading the code if you know the API anymore. Don't even think about understand code if there will be multiple extensions defining size()


    I think it was Google how submitted this proposal as a feature for Java 7, they seem to be pretty sure that it will be accepted as their collections API will profit from it to become eventually usable.


    In most codebases you can do without it by using replacements like common-collections or simple write one yourself (like we did @work). I feel it is a useless additional layer of complexity to the language that only promotes viral ideas

    Posted by: csar on November 29, 2007 at 03:18 PM

  • I agree with you. Creating the illusion of a new instance method that is really a static method is a very bad idea in my opinion. It makes the code much harder to read.

    Posted by: swpalmer on November 30, 2007 at 08:12 AM

  • A mistake was made in the code. It should be:

    for(int i=0;i<text.size();i++) {
    System.out.println(text.charAt(i));
    }

    Posted by: ossaert on December 03, 2007 at 05:05 AM

  • Re. use-site extensions, I am not totally convinced about the
    extension mechanism as proposed - maybe I need to know more about it.
    My particular concerns are:

    They look like they do dynamic dispatch, but they don't.
    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. Also you can pass this argument to multiple methods in a
    block, like a with clause (introducing with as a keyword would also be
    an option). 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. Similar to 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 Coleborne has suggested something similar but proposed .do.
    instead of -> and in Stephen's proposal there is no concept of passing
    to a block of statements and his proposal only works for statically
    imported static methods.

    Posted by: hlovatt on December 05, 2007 at 06:37 PM

  • RE Declaration-site extensions (but mention of use-site also)
    I think your concerns can re. multiple inheritance can be addressed
    using Traits. Traits are interfaces with method bodies, no
    constructors, and no fields. If a conflict arises due to multiple
    inheritance you must resolve the conflict.

    Consider:

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

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

    class XY implements X, Y { // Conflict - two foos
    public int foo() { return X.foo(); } // Conflict resolved by writing a foo - in this case it call X.foo
    }

    class XX implements X {} // No conflict - therefore no need to write a foo

    Class loader, not compiler, adds:

    public int foo() { return X.foo(); }

    To class XX.

    Method foo must be added to XX by the class loader so that new methods
    can be added to an interface without breaking old code (provided that
    they have implementations).
    The implementation technique proposed for traits (class loader adds methods) could be used to allow the user to add methods also (Josh Bloch’s point that it would be nice if the user of an API could add methods). EG:

    extension MyList extends List {
    sort() { …. };
    }

    Then in the main class (or at least before List is used):

    import MyList; // Not only tells the compiler to use MyList in the file but also extends List at runtime - file that imports MyList must be loaded before a file that uses List - OK to multiply import MyList

    Posted by: hlovatt on December 05, 2007 at 06:46 PM

  • 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(); };

    Posted by: hlovatt on December 13, 2007 at 02:29 PM





Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds