Virtual Extension By Inversion of Responsibility (Second Draft)

Alex Buckley alex.buckley at oracle.com
Fri Jun 18 17:06:41 PDT 2010


Thank you Collin. While the Sun Contributor Agreement covers source code 
submitted to an OpenJDK project, it's important that non-source code 
submissions also be licensed to Oracle appropriately; see:

http://openjdk.java.net/legal/terms.html
  4. THINGS YOU SUBMIT TO THE SITE.
   c. Other Submissions.

Without wishing to reprise the debates from December 2009 on use-site v. 
declaration-site extension, it's interesting that your extension 
mechanism is a combination: consumers-who-implement must indicate their 
extension, and consumers-who-invoke must indicate their use of the 
extension. The only guy left out is the interface being extended, and 
that means your scheme needs only compiler support.

Alex

P.S. I assume the 'o' in your unsugared code is meant to be 'planets'.

On 6/18/2010 4:50 PM, Collin Fagan wrote:
> Here is a plain text version of my proposal. Also if it maters, I've 
> signed the Sun agreement to contribute to OpenJDK.
> 
> Thanks,
> 
> Collin
> 
> -----------------
> 
> 
> Virtual Extension By Inversion of Responsibility
> Collin Fagan
> Second draft, Jun 2010
> 
> Problem statement
> 
>     Once an API is published and in common use, it is difficult to 
> evolve the very top layers of an abstraction without breaking existing 
> code. Changes that would introduce new methods to an interface or new 
> abstract methods to an abstract class will break both source and binary 
> compatibility.
> The addition of lambdas to the Java language in JDK 7 presents API 
> designers with this same problem on a much larger scale.
> 
>     Currently in Java the most straightforward approach would be to add 
> new interfaces to the API. These new interfaces can then be implemented 
> by concrete or abstract classes. The downside is that to use this new 
> functionality you must either program to this new interface or play the 
> game of "cast and hope". Neither immediately presents itself as a 
> desirable option.
> 
> Virtual Extension
>    
>     This document proposes a special "cast with fallback" mechanism. 
> This replaces the "hope" of the "casting and hoping" with the certainty 
> of a default implementation. This inverts the responsibility of 
> providing this new functionality to the extender and not the base class.
> 
> Example Syntax:
> 
> List<String> planets = Arrays.asList("Earth","Mars","Venus");
> String result = planets->join(“,”);
> assert(result == "Earth,Mars,Venus");
>     Here join looks like a method call on the List interface using a 
> funny operator. The -> operator is syntactic sugar for the inconvenient 
> alternative code. The important part however is that the extension 
> method is a separate entity from the interface.
> 
> Other sugared examples:
> 
> // groovy style GString.execute()
> "cp file1.txt file2.txt"->execute();
>    
> // log a severe exception
> ex->severe();
> 
> // Null safe isEmpty from Commons Lang
> String x = null;   
> assert(x->isEmpty());
> 
> // Lambda sort:
> planets->sort(#(String a, String b)(a.length() - b.length()));
> Possible Desugarings
> 
> Static Methods:
> 
>     One possible way to translate the -> operator is through delegation 
> to a static method.
> 
>     // Sugared
>         String result = planets->join(“,”);
> 
>     // Unsugared
>         String result = JoinUtils.join(planets, “,”);
> 
> 
>     Lets assume we have the following interface.
> 
>     public interface Joinable{
>         String join(String sep);
>     }
> 
>     There needs to exist a mapping between the join method of this 
> interface and the static join method available in JoinUtils. Here is one 
> way one could declare such a relationship.
> 
>     @ExtentionSupport(supports=Joinable.class)
>     public final class JoinUtils{
>         ...
>     }
> 
>     This could be a new keyword but I've chosen to use an annotation. 
> Next there needs to be a static method whose name and signature matches 
> the join signature with one extra parameter. This extra parameter is the 
> "target" of the method. By convention this could be the very first 
> parameter., otherwise another marker is required. This marker might also 
> be useful for adapting existing static methods.
> 
>     public static String join(@ExtentionTarget List<String> list, String 
> sep){
>         ...
>     }
> 
>     Now that we've indicated which parameter we want the -> operator to 
> apply to, we need a way to let the compiler know where to look for the 
> join method. Static methods already have a system for including them in 
> the current scope, static import.
> 
>     //Sugared
>    
>     import static JoinUtils.*;
>     ...
>     List<String> planets = Arrays.asList("Earth","Mars","Venus");
>     String result = planets->join(“,”);
>     assert(result == "Earth,Mars,Venus");
>     ...
> 
>     // Unsugared
>    
>     import static Utils.*;
>     ...
>     List<String> planets = Arrays.asList("Earth","Mars","Venus");
>     String result = join(planets, “,”);
>     assert(result == "Earth,Mars,Venus");
>     ...
> 
>     Finally there is the possibility that the "target" object might 
> already be an instance of Joinable. To support this case the final 
> unsugared code would need to check.
> 
>     // Unsugared
>    
>     import static Utils.*;
>     ...
>     List<String> planets = Arrays.asList("Earth","Mars","Venus");
>     String result = null;
> 
>     if(o instanceof Joinable){
>         result = ((Joinable)o).join(",");
>     }else{
>         result = join(planets, “,”);   
>     }
>     assert(result == "Earth,Mars,Venus");
>     ...
> 
> Pros  
> - No change required to any interface or class.
> - Adding a keyword is not required.
> - Many currently static methods could be retrofitted to work.
> Cons
> - Encourages static methods.
> - Requires static import.
> - Possibly not a huge savings in typing for the conceptual weight.
> - Requires an instanceof and a cast
>  
> Delegating to a supporting object:
> 
>     Another possible desugaring of the -> operator is to delegate to a 
> supporting object. Below is an example of a "support object" for the 
> Joinable interface.
> 
> @ExtentionSupport(supports=Joinable.class)
> public class JoinableSupport<E> implements Joinable{
> 
>     private Collection<E> targetCollection;
>        
>     public JoinableSupport(Collection<E> targetCollection) {
>         this.targetCollection = targetCollection;
>     }
> 
>     @Override
>     public String join(String sep) {
>         .. join logic ..
>     }       
> }
>    
> Each support object would be required to have a single argument 
> constructor. This constructor argument would be a reference to the 
> object this functionality is being "applied" to.
> 
> // Sugared
> 
> List<String> planets = Arrays.asList("Earth","Mars","Venus");
> String result = planets->Join(“,”);
> assert(result == "Earth,Mars,Venus");
> 
> // Unsugared
> 
> List<String> planets = Arrays.asList("Earth","Mars","Venus");
> String result = new JoinableSupport(planets).join(planets, “,”);
> assert(result == "Earth,Mars,Venus");
> 
> 
> // Sugared
> 
> List<String> planets = Arrays.asList("Earth","Mars","Venus");
> 
> planets->ForEach(#(String target){
>     target->Print();
> })
> 
> // Unsugared (with an SAM style lambda expansion *)
> 
> new ForEachSupport<String>().invoke(planets, new Block<String>(){
>     @Override
>     public void invoke(String target) {
>         new Print().invoke(target, Void);
>     }
> });
> * I'm not advocating that lambdas be implemented like this. I just need 
> to translate them to something remotely familiar to talk about extension 
> methods.
> 
>     Again there is the possibility that the "target" object might 
> already be an instance of Joinable. To support this case the final 
> unsugared code would need to check.
> 
>     // Unsugared
>     ...
>     List<String> planets = Arrays.asList("Earth","Mars","Venus");
>     String result = null;
> 
>     if(o instanceof Joinable){
>         result = ((Joinable)o).join(",");
>     }else{
>         result = new JoinableSupport(planets).join(“,”);
>     }
>     assert(result == "Earth,Mars,Venus");
>     ...
>    
> Pros   
> - No change required to any interface or class.
> - Adding a keyword is not required.
> - Does not encourages static methods.   
> - Does not requires static import.   
> 
> Cons
> - Need to manage number of objects created.
> - Assumes that importing a "support class" makes it available to the -> 
> operator.
> 
> Lambda Delegation
> 
>     If lambdas exist then another desurgaring becomes possible, an 
> Extension Lambda.
> 
> // Sugared
> 
> List<String> planets = Arrays.asList("Earth","Mars","Venus");
> 
> planets->forEach(#(String target){
>     target->print();
> });
> 
> // Unsugared (to lambda):
> 
> List<String> planets = Arrays.asList("Earth","Mars","Venus");
> 
> forEach.(planets, #(String target){
>     print.(target);
> });
>    
> // Lambda declaration
> #void(@ExtentionTarget Collection<T>, #(T)) forEach<T> =
>     #(Collection<T> collection, #(T) lambda){
>     for(T item: collection){
>         lambda.(item);
>     }
> };
> 
>     We again use an annotation on the parameter we want the -> operator 
> to bind to. One thing not explicitly stated in the straw man proposal is 
> whether a lambda is allowed as a top level construct. Currently Java 
> supports class, interfaces and enum. If lambdas are treated as top level 
> constructs then they could be imported directly. If not then we might 
> have to go back to statically importing some public static final fields 
> that refer to lambdas. Also not specified in the straw man proposal is 
> the interaction of lambdas and polymorphism. Is it possible for a 
> subclass to override the default implementation of a lambda?
> 
> Pros   
>   No change required to any interface or class.
>   Adding a keyword is not required.
>   Creates a similar amount of overhead as a lambda.   
> Cons
>     May require static import?
>     How can you override a lambda in a subclass?
> 
> Final thoughts
> 
>     All three transformations are an attempt to remove the need to 
> evolve interfaces. Each try to push the responsibility into more 
> flexible constructs. I consider nothing final, not the syntax, not the 
> details of the de-sugaring, not anything. I hope this document will spur 
> positive discussion in this direction and I welcome all comments.
>    
>   
> 
> On Fri, Jun 18, 2010 at 1:58 PM, Alex Buckley <alex.buckley at oracle.com 
> <mailto:alex.buckley at oracle.com>> wrote:
> 
>     Hi Collin,
> 
>     If lambda-dev is to discuss your proposal, you will need to send the
>     text of the proposal to lambda-dev, rather than pointing to an
>     external site. Thanks.
> 
>     Alex
> 
> 
>     On 6/17/2010 8:09 PM, Collin Fagan wrote:
> 
>         Hi Everyone,
> 
>         I've put together a second draft of my proposal for virtual
>         extension
>         methods.
> 
>         http://www.box.net/shared/r24vvgma5p
> 
>         And yes *this* time they are really virtual. I welcome all comments.
> 
>         Thanks,
> 
>         Collin
> 
> 


More information about the lambda-dev mailing list