Lambda and method reference inference

Remi Forax forax at univ-mlv.fr
Sat Dec 8 04:09:42 PST 2012


On 12/07/2012 11:10 PM, Dan Smith wrote:
> On Dec 6, 2012, at 11:56 AM, Remi Forax <forax at univ-mlv.fr> wrote:
>
>> I think i've already raised this point in August during the face to face meeting and at that time nobody care, maybe this time, I will have more chance :)
>>
>> with
>>     interface A<T> {
>>       T foo(T a, T a2);
>>     }
>>
>> this compile:
>>     A<Integer> b = Integer::plus;    // with plus defined as int plus(int,int)
>>
>> but not this one:
>>     A<Integer> c = (int x, int y) -> x + y;
>>
>>
>> Can we, please, have the same rules for both method references and lambdas that have their type of parameters specified ?
> For what it's worth, I have been hanging on to this idea as something to circle back on.
>
> It would be useful if you could expand on your motivation.  I'm guessing it is one (or both) of:
> 1) You don't like the model we have and prefer yours.
> 2) You want direct control over the signature of the method generated by the compiler, for performance reasons.

it's 2) but not for performance reason but for semantics reason, given 
that in Java, Integer and int doesn't have the same semantics, as a user 
I want the to specifies that a lambda takes two ints and not two 
integers. You can argue that users can insert cast to int where needed 
but from experience, users poorly understand where to put cast in the 
middle of the code and given that in our case types are implicit, it 
doesn't help.

About performance, that true that if the signature only use primitive 
type, it will be easier for the VM to remove all boxing if the lambda 
proxies generated use method handles because the current optimizations 
done by Hotspot is enough, we don't have to rely on some hypothetical 
optimization not yet written.

>
> Where we left this last is that we like the model ("we" meaning Oracle folks, plus I haven't heard much opposition from other EG members), but it's something to reconsider if we find that performant code requires some manual intervention for tuning (e.g., minimizing boxing).

see above, performance is part of the equation but not the important 
one. Being able to specify the right semantics is more important.

>
> Also, it would be useful to define the scope of what you're after:
> 1) Parameter types can be boxed/unboxed
> 2) Parameter types can be widened (or anything else legal in an invocation context)
> 2') Thus, type parameters for generic descriptors can be widened to their bounds
> 3) Parameter types can be grouped via varargs
>
> Consistency with method references would suggest doing all of these...

yes, here consistency is the important word.
The JLS defines two sets of rules for compatibility, invocation 
conversions and overriding conversions.  I don't see why we need to 
invent a new one.

The lambda spec should just make a difference between lambda with 
parameter types that should use invocation conversions and lambda 
without parameter types that should use overriding conversions.
Or said differently, in a perfect word, lambda and method reference 
should all use invocation conversions but because we have to infer 
parameter type if lambda doesn't specify parameter types, we prefer to 
use overriding conversions to make the inference algorithm less surprising.

>
> ---
>
> Addressing the model: Brian summarized the model succinctly—lambdas _override_ a functional interface method; method references are _invocations from_ a functional interface method.  In other words, this:
>
> Integer::plus
>
> is shorthand for this:
>
> (x,y) -> Integer.plus(x, y)
>
> If the types of 'x' and 'y' are 'Integer', that's fine.  It is not necessary that 'x' and 'y' have type 'int'.
>
> You point out that certain adaptations on the parameters can't be performed, and you have to do that yourself inside the body (e.g., cast Integer to int or call a method that will do the conversion automatically).  In the rare circumstance in which such conversions have semantic consequences, that's true enough.

I disagree, it will not be that rare because features all goes in the 
same direction.
   - there is no way to declare a collection of primitive type
     => so user use wrapper type and usually it works
  - collection API use lambdas to filter/map so
     => so users use lambda to specify boolean expression like equality test
  - lambda doesn't require to specify type of parameters
     => so users will not see that the inferred type is boxed

>
> We have a pretty good story for widening where the target type uses wildcards.  For example:
>
> Predicate<? super Integer> p = (Number n) -> ...; // legal
>
> Here, we infer that the instantiation of Predicate is Predicate<Number>.
>
> So probably the main practical impact of the problem, if there is one, is with respect to boxing/unboxing.  If we eventually support primitives as type arguments, that problem may go away, too...

I think i will be dead before Java will truly support primitive types in 
parameter types.  The problem is the VM/runtime not the compiler.

>
> Brian points out that equality is more useful for inference than convertibility.  For example, the above wildcard inference problem essentially tries to find an alpha such that Predicate<alpha> is a valid target type.  With equality constraints, this means, trivially:
>
> alpha = Number
>
> (More generally, we could have something like a descriptor parameter type Foo<? super Bar<alpha>> and a lambda parameter type Foo<? super Bar<Monkey[]>>; it's always just pattern matching to solve for alpha.)
>
> In contrast, with convertibility constraints, we get
>
> alpha :> Number
>
> And then we need to work out whether there are other constraints on alpha (e.g., the declared bounds), and there's a bunch of complexity to solve for the "best" alpha and hope we're right.  Of course we know how to do this, because that's how we handle method invocations, but in the context of deciding what the descriptor of a target type is, it's sure nice to keep things simple.

yes, when inferring the type of the parameters, it's better to keep the 
things simple for the user too. I don't know if pattern matching is 
enough but at least the error message will be understandable.

>
> Similarly, we looked into contravariance for the most-specific algorithm.  If you look at my write-up about overload resolution from a few months ago (jsr-335-eg list, subject "Method invocations & applicability", subheading "Functional Interface Parameter Types"), you'll see discussion about how both contravariance and covariance are problematic.  So we settled on requiring that the parameter types be the same.  Turns out, that was a really good choice.  Because if the parameter types are different, we can't make any guarantees about the interpretation of the body.  As we've explored the "most specific" analysis and decided, e.g., that we want to know whether certain lambda returns produce primitives or references, it's really nice to have a stable interpretation of the body with which to work.
>
> Summarizing, the idea that parameter types should match with something else pops up quite a bit in our design, and typically frees up the compiler to do more useful things.  It's not just an anomaly in the lambda compatibility rules.

The idea that the Inference use less conversions than the one specified 
by the method invocation algorithm seems a good practical choice. I can 
not say if it's a good idea or not. But i don't see how it's related to 
the case that doesn't require inference of the parameter types.

>
> —Dan

Rémi



More information about the lambda-spec-observers mailing list