Overload resolution simplification

maurizio cimadamore maurizio.cimadamore at oracle.com
Sun Aug 11 02:50:54 PDT 2013


On 10-Aug-13 10:58 PM, Remi Forax wrote:
> It's not less magic. It's no magic. 
This is a bit unfair. I think there's still plenty of magic going on. 
Note that the path that led us there is that we were uncomfortable in 
having the compiler to pick one method over another because of some 
weird error in some implicit lambdas. Now - if you have something like:

m(Function<String, Integer> f)
m(IntFunction<String> f)

And a lambda like

m(x->1)

it could be trivial to see that the second method is the one you want.

On the other hand, consider the following overload of _non generic_ methods:

m(Function<String, Integer> f)
m(IntFunction<Integer> f)

If we start accepting this (overload between non-generic methods - so 
everything is ok, no?) - then we open up can of worms in which an error 
in one speculative type-check will cause the method not to be applicable 
- i.e. :

m(x->x.length()) //no member length() in Integer, so the first method is 
selected
m(x->x.intValue()) //no member intValue() in String, so the second 
method is selected

This was what the compiler was doing - and EG expressed some concerns 
with this - and for a good reason I think. A seemingly innocuous 
refactoring of a lambda body can trigger a change in overload resolution 
of the enclosing method call.

In other words, even when there's an overload between non-generic 
methods, it gets very muddy very soon as soon as you consider all 
consequences of 'adding more magic'. Suddenly you have to start thinking 
about errors in the lambda body to reason about method applicability, 
and that's brittle (in fact, even though an earlier version of flatMap 
was able to disambiguate because of this, we decided NOT to rely on this 
as too magic/brittle).

So, the only meaningful question here is - where should we draw the 
line? I see three options:

1) Allow non-generic overloads, provided each overload forces same 
choice on lambda parameter types
2) Expand on 1a, implementing a more complex logic that would also work 
on generic methods
3) Disallow implicit lambdas when overloads of the same arity are available

I think 2 was considered also too complex, as the analysis would have to 
treat inference variables that depend on the return type in a different 
way (i.e. Comparator.comparing won't work) - and that's surprising. So 
that's mostly a dead end.

The choice is between 1 and 3. As a result of the current 
simplification, we are in 3. Now, I'm not totally against 1 - I think 
it's a legitimate position, but there are things about that approach 
that worry me:

*) inconsistencies between non-generic vs. generic methods
*) hard to parse when parameter types are ordered in different ways with 
different SAM types (see my earlier example on this list)
*) will miss ambiguities when passing a lambda to _semantically_ 
unrelated overloads - i.e.

m(Function<String, Integer>)
m(Predicate<String>)

This example passes the rule that all overloads force same choice on 
lambda parameters. Should we accept a method call like the following?

m(x->foo(x))

Well, if we follow that scheme, the answer is yes - and the candidate is 
chosen depending on the return type of foo(String). I think that this 
choice, while legitimate, is already 'too magic' - we have to very 
different targets there - with entirely different semantics; I think in 
such cases I would be more comfortable in having a more explicit form of 
disambiguation at the call site.

Last point - it looks to me that all cases in which we would prefer 1 
over 3 have to do with primitive specialization of SAM. On the one hand, 
the Java type-system features this split between primitive and reference 
types - so that's what we get; on the other hand, if (big if :-)) we had 
some form of primitive vs. reference unification, how many cases would 
be left that 1 could support that 3 cannot? Are we comfortable in 
_permanently_ adding more complexity to an already very complex area of 
the language, that could then be useless (at best, or even fire back) 
if/when the type-system is made more uniform?

Maurizio






More information about the lambda-spec-observers mailing list