Overload resolution simplification

Remi Forax forax at univ-mlv.fr
Tue Aug 13 14:07:54 PDT 2013

On 08/11/2013 11:50 AM, maurizio cimadamore wrote:
> 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.

Well Ia gree,
just nitpicking about the semantics, in a perfect world all overloads 
are semantically equivalent, otherwise you shall not use overloads.

> 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-experts mailing list