Lambda return types and overloaded methods
Maurizio Cimadamore
maurizio.cimadamore at oracle.com
Tue Aug 16 10:17:40 UTC 2016
Hi Chris,
what you are observing is by design - to keep things tractable, we
deliberately decided to keep overload resolution a function of the
argument expressions, and to only look at the return types after
inference. In fact, javac won't even look at the body of a lambda, or a
method reference or conditionals if such expressions are considered
_not_ pertinent to applicability (see JLS section 15.12.2.2 for a formal
definition). In short, an argument expression is pertinent to
applicability unless it is an implicit lambda (a lambda w/o explicit
parameter types) or an inexact method reference (a method reference
matching multiple overloads) - or a conditional whose either
sub-expression is one of those.
So, when you have argument expressions that are not pertinent to
applicability, your only hope against ambiguity error is the loose
applicability check described in JLS 15.12.2.1 - potential
compatibility. This is a much looser check which simply validates that
the lambda (or method reference) has an arity that is compatible with
that of the target functional interface (for method reference we also
look at void vs. value compatibility).
Sp, all your implicit lambda cases are ambiguous, because your targets
are Predicate<String> and Function<String, String> - i.e. both are
functional types accepting a String (arity 1). Given we are not allowed
to look at the lambda body at this stage (the lambda is not pertinent to
applicability), we have to give up.
In the case of the method references, you have that both String::isEmpty
and String::trim are _exact_ method references - they are unambiguous,
so the return types of such methods can indeed be used to disambiguate.
The same cannot be said for String::toUpperCase which has two matches -
so both Predicate<String> and Function<String, String> are potentially
compatible with String::toUpperCase (given that one of the overloads for
String::toUpperCase matches the arity of the function types) - but the
method reference expression is then discarded from further checking
because it is an inexact method reference, so the return type cannot be
looked at.
If this behavior seems too strict, please keep in mind that these rules
have been discussed extensively at the various lambda EG meetings; at
some point we had a more powerful overload resolution algorithm - but
there was an unanimous consensus to back out from it because its
behavior was very hard to explain to users (the compiler would
speculatively check a lambda body against each overloaded method, and
discard those not compatible - this leads to a combinatorial explosion
of checks in nested cases - and is generally an hard-to-follow process
for a non-stack based human ;-)).
I concede that, in these particular cases, the behavior of pertinent to
applicability/potential compatibility is a little phony - in the case of
lambda, all overloads accept a functional interface which dictate a
String parameter - so, there seems to be no ambiguity as to how the
lambda body should be interpreted. A similar consideration holds for the
method reference case where, after all, both overloaded toUpperCase
methods return String, so why not using that information to prune the
space of applicable methods (since Predicate<String> clearly does not
return a String) ? While these might all be good incremental
improvements over what we have, I must stress that this is not the
language we have today.
Cheers
Maurizio
(*) this is not totally correct - in fact return types are also used for
disambuguation when multiple methods are applicable - but for that to
apply the lambda/method reference must be pertinent to applicability -
i.e. an explicit lambda, or an exact method reference.
On 16/08/16 10:21, Greenaway, Chris wrote:
>
> There seems to be a problem with matching the return type of a lambda
> to the type required by an overloaded method when the lambda parameter
> isn't explicitly typed.
>
> For example, the code below has compilation failures on the lines that
> are marked with "// Error". All other lines are acceptable.
>
> There also seems to be an issue with method references when there are
> multiple methods with the same name but different arity - see the line
> marked "// Error 8".
>
> Chris Greenaway.
>
> import java.util.function.Function;
>
> import java.util.function.Predicate;
>
> public class LambdaOverloading {
>
> public static void main(String[] args) {
>
> LambdaPredicate predicate = new LambdaPredicate();
>
> predicate.run((String s) -> s.startsWith("X"));
>
> predicate.run((String s) -> s.isEmpty());
>
> predicate.run((String s) -> true);
>
> predicate.run(s -> s.startsWith("X"));
>
> predicate.run(s -> s.isEmpty());
>
> predicate.run(s -> true);
>
> predicate.run(String::isEmpty);
>
> LambdaFunction function = new LambdaFunction();
>
> function.run((String s) -> s.substring(1));
>
> function.run((String s) -> s.trim());
>
> function.run((String s) -> s.toUpperCase());
>
> function.run((String s) -> "");
>
> function.run(s -> s.substring(1));
>
> function.run(s -> s.trim());
>
> function.run(s -> s.toUpperCase());
>
> function.run(s -> "");
>
> function.run(String::trim);
>
> function.run(String::toUpperCase);
>
> LambdaOverloaded overloaded = new LambdaOverloaded();
>
> overloaded.run((String s) -> s.startsWith("X"));
>
> overloaded.run((String s) -> s.substring(1));
>
> overloaded.run((String s) -> s.isEmpty());
>
> overloaded.run((String s) -> s.trim());
>
> overloaded.run((String s) -> s.toUpperCase());
>
> overloaded.run((String s) -> true);
>
> overloaded.run((String s) -> "");
>
> overloaded.run(s -> s.startsWith("X")); // Error 1
>
> overloaded.run(s -> s.substring(1)); // Error 2
>
> overloaded.run(s -> s.isEmpty()); // Error 3
>
> overloaded.run(s -> s.trim()); // Error 4
>
> overloaded.run(s -> s.toUpperCase()); // Error 5
>
> overloaded.run(s -> true); // Error 6
>
> overloaded.run(s -> ""); // Error 7
>
> overloaded.run(String::isEmpty);
>
> overloaded.run(String::trim);
>
> overloaded.run(String::toUpperCase); // Error 8
>
> }
>
> private static class LambdaPredicate {
>
> public boolean run(Predicate<String> predicate) {
>
> return predicate.test("test");
>
> }
>
> }
>
> private static class LambdaFunction {
>
> public String run(Function<String, String> function) {
>
> return function.apply("test");
>
> }
>
> }
>
> private static class LambdaOverloaded {
>
> public boolean run(Predicate<String> predicate) {
>
> return predicate.test("test");
>
> }
>
> public String run(Function<String, String> function) {
>
> return function.apply("test");
>
> }
>
> }
>
> }
>
> This communication contains information which is confidential and may
> also be privileged. It is for the exclusive use of the intended
> recipient(s). If you are not the intended recipient(s), please note
> that any distribution, copying, or use of this communication or the
> information in it, is strictly prohibited. If you have received this
> communication in error please notify us by e-mail and then delete the
> e-mail and any copies of it.
> Software AG (UK) Limited Registered in England & Wales 1310740 -
> /*http://www.softwareag.com/uk* /
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.openjdk.java.net/pipermail/compiler-dev/attachments/20160816/090abb6e/attachment-0001.html>
More information about the compiler-dev
mailing list