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