Overload resolution simplification
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)
And a lambda like
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)
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
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.
This example passes the rule that all overloads force same choice on
lambda parameters. Should we accept a method call like the following?
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?
More information about the lambda-spec-observers