method references: type inference, toString()

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Wed Feb 22 08:28:28 PST 2012


On 22/02/12 14:50, Paul Holser wrote:
> Thanks for the prompt response, and for the reminder about the cast.
> Eager to see how things shake out with type inference!

As Brian said, details of type-inference are being figured out as we 
speak. The design space is, as you can imagine, fairly big, and 
solutions might vary from a full-blown global type-inferencer  (a la ML) 
to strategies that work in a more local environment. We think that the 
latter strategies have better chance of succeeding in the Java ecosystem 
- one important consequence of adopting a global inference scheme is 
that diagnostics are usually pretty bad when things go wrong, as some 
inference error in some nested expression might trigger a type-error at 
a later stage in a seemingly unrelated expression.

We would also like to maintain the invariant that the outcome of 
overload resolution should _not_ depend on the target type - i.e. the 
process for selecting an overloaded method is defined independently from 
the target type - same applies for the most specific routine.

This means that in your case, where you have the following expression:

assertThat(new BarredUpFoo(), Predicates.matches(Foo::isBar));


A local type-inferencer will not, in the general case, be able to help 
you with this example, as inference constraints on the first arguments 
(new BarredUpFoo()) cannot be used to influence overload resolution for 
the call to Predicates.matches(...) - nor we can use the second formal 
of 'assertThat' in order to discard potential overload candidates for 
Predicates.matches. This is the price to pay for locality.

Another problem in this particular example, is that the formal of 
Predicates.matches contains inference variable - that's why you are 
getting the 'cyclic inference error' messages: the compiler is waiting 
for info in the target type to be able to proceed with the attribution 
of the method reference (the same applies if a lambda has no explicit 
parameter types) - since the target type is not a concrete type, you 
have an inference cycle there.

What happens when there's such a cycle? There are, again, several 
strategies that could be exploited - one could have the compiler to 
infer all inference variable in the target type, and then proceed with 
normal attribution of the lambda/method reference. An important point 
here, is that we should instantiate inference variable in the target 
type only _after_ we checked all other argument types, otherwise the 
inferred type will be dependent on the order in which arguments are 
checked.

A simpler solution is to have the compiler reporting the error (cyclic 
inference), and perhaps give a detailed explanation of what is the type 
information that is missing. While this approach is significantly 
simpler (i.e. it avoids extra-complexity burden on overload resolution), 
we believe it has a good cost vs. benefit ratio. Moreover, there are 
things that we can do in order to minimize the occurrences of such 
messages - for example, in the case above, since Foo::isBar is 
unambiguous, the method reference could be type-checked even in the 
absence of concrete argument types from the target functional descriptor.

Bottom line: as far as type-inference is concerned, our story can be 
summarized as follows:

*) keep it simple
*) enhance inference in 'obvious cases'
*) provide enough info to the user so that it will be easy to recover 
from inference errors

Maurizio
>
> --p
>
> On Tue, Feb 21, 2012 at 5:07 PM, Brian Goetz<brian.goetz at oracle.com>  wrote:
>>> 1) Should the usage of Foo::isBar as the predicate fed to
>>> PredicateMatcher.matches() compile successfully? I'm hoping not to
>>> have to perform the SAM conversion by hand:
>>>
>>>      Predicate<Foo>    bar = Foo::isBar;
>>>      assertThat(new BarredUpFoo(), PredicateMatcher.matches(bar);
>>
>> Without answering the question on whether the inference should succeed or
>> not (the exact details of type inference are currently in flux), I'll point
>> out that there is a less intrusive form of "by hand" that can be used to
>> provide the needed additional type info to the compiler -- use a cast to
>> provide the target type.  This is less intrusive that having to name a local
>> variable.
>>
>>   assertThat(new BarredUpFoo(),
>>              PredicateMatcher.matches((Predicate<Foo>) Foo::isBar));
>>
>>
>>> 2) Wondering if it wouldn't be too much trouble to have method
>>> references respond to toString() with something like "Foo::isBar" or
>>> "method isBar on class Foo", instead of, e.g. "Main$1 at a30a4e". It'd
>>> make the output of PredicateMatcher.matches() really slick.
>>
>> Yes, the details of this are being actively discussed in the EG, and is a
>> desirable goal.  Lots of details to work out, of course.



More information about the lambda-dev mailing list