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