Overload resolution simplification

Ali Ebrahimi ali.ebrahimi1781 at gmail.com
Wed Aug 14 01:03:55 PDT 2013


Hi,


On Sun, Aug 11, 2013 at 2:20 PM, maurizio cimadamore <
maurizio.cimadamore at oracle.com> 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.
>
> 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?
>
> Sorry, Maurizio, I can't agree on this.
I think we should be consistent in all cases. So if we want unification why
we add primitive specialization of SAMs and Streams. May be they are hacks
as mangling method names to overcome compiler's weaknesses and will be
deprecated in future.
So better we have a clear view from future and make decisions based on that.
And one thing that I don't get is why java can't go C#'s way in this case?


Regards,
Ali Ebrahimi


More information about the lambda-spec-observers mailing list