method references: type inference, toString()
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
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
> 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
>> 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