New JEP: Concise Method Bodies

Brian Goetz brian.goetz at oracle.com
Mon Oct 8 16:50:56 UTC 2018


> The challenge seems to be finding the right intuition.

Yep.  The intent is to suggest that the behavior on the RHS is a 
"constant" of some sort with respect to some context, so that 
"assigning" it is definitional rather than mutative.  But this is still 
fuzzy, so we need to narrow this down.  The narrowest interpretation of 
constant (#1) would eliminate some of the most useful motivating cases 
for the feature, such as:

     class C implements Comparable<C> {
         private lazy static final Comparator<C> comp = Comparator.of(...);

         public int compareTo(C other) = comp::compare;
     }

or

     class UnmodifiableFooWrapper<T> implements Foo<T> {
         private final Foo<T> underlying;

         public void fooMethod1(args) = underlying::fooMethod1;
         public int fooMethod2(args) = underlying::fooMethod2;
         public long fooMethod3(args) = underlying::fooMethod3;

         public String fooMethod4(args) {
             // do something extra here
             return underlying.fooMethod4(args);
         }
     }

If we were to eliminate these cases, much of the benefit goes away.

Your #2 would admit the first example (because the comparator field is 
final), but eliminate the second; #3, #4, and #5 would also admit the 
second.

Of all of them, #6 seems the worst, because it undermines the 
"definitional" intuition I was going for.

> 1) One interpretation is that the method reference must be a 
> constant—something that can be translated into a JVM linkage 
> instruction without executing any code. (Meanwhile, on the JVM side 
> we've explored some features for declaring concrete methods without a 
> Code attribute.)
>
> 2) Another interpretation is Brian's here: the evaluation of a method 
> ref's receiver parameter occurs at class initialization, as if there 
> were an initialization of a static field. This suggests that "linkage" 
> of the method (i.e., computation of a function-like entity 
> representing the body) occurs at class initialization.
>
> 3) Or, also suggested above: the evaluation occurs in a constructor, 
> again as if a (in this case, instance) field were initialized with the 
> expression.
>
> 4) The evaluation occurs after the constructor has completed, making 
> all fields—including those initialized in the constructor—available.
>
> 5) The evaluation occurs just before first method invocation.
>
> 6) There is no "linkage" step, the method reference is re-computed on 
> every invocation. Method parameters are in scope. This is the 
> "syntactic sugar" approach, and the one Kevin is taking issue with.
>
> - It's not totally clear what we do with 'this'. Kind of seems like it 
> should be treated as the first parameter to be passed, but there are 
> also examples in the JEP that ignore it. And is it allowed to be 
> referenced by the receiver expression?
>
> void reverse() = Collections::reverse; // invoke 
> Collections.reverse(this)?
> int length(String s) = String::length; // invoke s.length()?
> void altMethod() = ThisClass::method; // invoke this.method()?

In existing SAM conversion, we're willing to treat `Foo::bar` as either 
a static method or a bound instance method (as long as we can 
disambiguate.)  I think what's new here is that you're saying there's 
some ambiguity between whether we should infer a functional interface 
solely from the method arguments and return, or whether (for an instance 
method) we should be willing to consider the receiver as an extra 
parameter.  The latter would enable a category of extension-method-like 
use cases, where we'd have static methods which take a leading 
pseudo-receiver, designed for use as "injectable behavior"?  And of 
course doing so would expand the territory where the compiler couldn't 
pick an overload, since we historically did overload selection _after_ 
the SAM type was known. This would requiring doing it against both 
interpretations, and hoping exactly one answer pops up.

> void altMethod2() = this::method; // legal?

If we're allowing an instance field as a receiver, its a little weird 
not to allow `this`, since `this` has basically the same degree of 
constancy as a final instance field.

> - Delegation typically depends on an instance field provided as a 
> constructor parameter, which won't work with (1)-(3), and if the 
> delegation target is meant to be mutable, won't work with (4)-(5) either.
>
> Foo target;
>         int m1() = target::m1;
> int m2() = target::m2;
>
> - Ideally, we would not like to further complicate the class/instance 
> initialization story by adding new execution timing, as in (4) or (5). 
> What Brian's story ((2) and (3)) has going for it is that we already 
> have the infrastructure to describe the timing of these evaluations.
>
> - We can simplify things considerably by prohibiting the expression 
> form—must use Type::name. Effectively, (1) but without support for 
> constant expressions. That means giving up on some use cases.
>

In any case, an effective simplification that includes the desired use 
cases and stays within the intendd intuition would be to only allow 
bound method refs when the receiver is a compile-time constant or a 
final field of the current class (or possibly `this`.)


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.openjdk.java.net/pipermail/amber-spec-experts/attachments/20181008/cf3c3b59/attachment.html>


More information about the amber-spec-experts mailing list