Virtual Extension By Inversion of Responsibility (Second Draft)
Collin Fagan
collin.fagan at gmail.com
Fri Jun 18 17:20:12 PDT 2010
I have read section C and agree to the terms.
Collin
On Fri, Jun 18, 2010 at 7:06 PM, Alex Buckley <alex.buckley at oracle.com>wrote:
> 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