Lambda and method reference inference

Remi Forax forax at univ-mlv.fr
Thu Dec 6 15:37:21 PST 2012


On 12/06/2012 11:24 PM, Brian Goetz wrote:
> In the assignment case, there's usually not a problem.

I've used assignment just because it's easier to see the target type so 
it's easier to read the mail.

> But nearly all the pain (which we're still not done with, by the way) 
> has been where type inference and method overload selection interact.  
> You've seen the "cyclic inference" errors in plenty of cases where it 
> "should" be "obvious" what the right answer is.

I claim there is no cyclic inference when you know the type of the 
formal parameters is know.
Cyclic inference is another problem, and IMO should be tackle by 
considering the result type but that's another debate.

We have 3 forms of pseudo lambdas:
1) the lambda with type of the parameters known, that the simplest form 
because from the type of the parameter you can find the return type by 
typeching the body using the classical algorithm (no inference here). 
When you have the return type, you can try all overloads using the same 
algorithm that the one used to invoke a method i.e first finding the 
applicable method (once without boxing and varags, once with boxing and 
no varargs and once with boxing an varargs). This is the very same 
algorithm as the one introduced by Java 5 (JLS2 if you prefer). So yes 
you can have ambiguities, exactly like you can have ambiguities when you 
call a method that is overloaded.

2) method reference, here because we don't specify the type of the 
parameter, the method reference can have several overloads and the 
method that takes the method reference can have several overloads. Here 
we can have cyclic inference problems. Despite that, method reference 
use the assignment conversion rules describe above.

3) lambda with no type of parameters, again here we can have cyclic 
inference. The inference is more complex. I understand why it's better 
to limit ourselves to only overriding conversions.


>
> By giving more latitude to the compiler to consider box and unbox 
> conversions, you deprive the compiler of type information with which 
> to make better inference decisions.  Consider the following simple 
> example:
>
> Given overloads:
>   m(IntIntFn)
>   m(Fn<Integer,Integer>)
>
> and a call
>
>   m((int x, int y) -> ..., null)
>
> Under the current rules, with the explicit types, we can discard the 
> second candidate early.

The rules of assignment conversions/invocation conversions will also 
discard the second candidate early because the first phase is to lookup 
for applicable method without considering boxing. So your point is moot.

>   The selection of the target type IntIntFn may further give us type 
> information which can be used elsewhere in inference.  If we have to 
> allow for the idea that we might be calling either version of m, not 
> only do we have less information, but we also are more likely to get 
> into the stability problems that the EG quite broadly agreed we wanted 
> to stay away from (where small changes here can silently change 
> overload choices.)
>
> By picking the assignment case, you've ignored 99% of the complexity. 
> All the complexity is in the interaction of inference and overload 
> resolution.  If this restriction gives us even 1% better results here 
> -- and it does -- it's totally worth it in my book.

Rémi

>
> On 12/6/2012 5:01 PM, Remi Forax wrote:
>> On 12/06/2012 10:03 PM, Brian Goetz wrote:
>>> We did care -- a lot -- but in the other direction :(
>>>
>>> This is one of those things that seems obvious at first but in reality
>>> has a poor cost-benefit balance.
>>>
>>> First of all, in your example, you can get what you want with:
>>>
>>>       A<Integer> c = (x, y) -> x + y;
>>
>> it's not what I want, you can not do the same trick with:
>>    A<Integer> c = (int x, int y) -> x == y;
>> or
>>    A<Integer> c = (int x, int y) -> { list.add(x, list.remove(y)); }
>> or any calls that makes a difference between int and Integer.
>>
>>>
>>> If you take the trouble to provide explicit parameter types, you are
>>> saying "I want these exact types."  I can understand why you would
>>> want the compiler to "just do what I mean", but allowing this degree
>>> of flexibility unfortunately has a cost, and one of those costs is
>>> more inference failures in other cases.
>>
>> can you be a little more specific, which failures ?
>>
>>>
>>> At the same time, the benefit is low -- to fix the problem, either
>>> leave out the types and let the compiler infer them, or use the exact
>>> types you need (there's a good chance your IDE will provide them for
>>> you anyway.)  It's an easy and local fix.
>>
>> The compiler infers the wrong type and I can't using the right type that
>> exactly my problem.
>> The best I can do is introduce a temporary method which is really non
>> intuitive
>>
>> void foo(int x, int y) { list.add(x, list.remove(y)); }
>>
>> A<Integer> c = (int x, int y) -> MyClass::foo;
>>
>> and sorry, I don't believe that the IDE will cope with that (at least
>> not before 2 years).
>> This 'feature' make even the job of IDE writer far more complex, by
>> example refactoring a lambda to a method reference and vice-versa will
>> have to deal with stupid discrepancies like this one.
>>
>>>
>>> We do allow this flexibility for method references because the user
>>> has no control over the types of the referred-to method the way he
>>> does with lambda parameter types.  And method references exhibit more
>>> inference failures.
>>>
>>> The semantics are modeled on the following intuition:
>>>  - typing for lambda expressions is like overriding a method;
>>>  - typing for method references is like calling a method.
>>
>> I understand why when inferring a lambda with no specified type for
>> formal parameter should be restricted to overriding rules but I don't
>> understand why lambda with specified types for formal parameters has to
>> have different rules different than the one for method reference.
>>
>> cheers,
>> Rémi
>>
>>>
>>>
>>> On 12/6/2012 1:56 PM, Remi Forax 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 ?
>>>>
>>>> cheers,
>>>> Rémi
>>>>
>>



More information about the lambda-spec-experts mailing list