Lambda return types and overloaded methods

Greenaway, Chris Chris.Greenaway at softwareag.com
Tue Aug 16 13:58:55 UTC 2016


I agree with you. After following your highlighting of those pieces of 15.12.2.1, I can see why the examples I gave do not compile.

However, there seems to be a conflict between 15.12.2.1 and 15.27.3.

In 15.27.3, it says that a "lambda expression is compatible in an ... invocation context ... with a target type T if T is a functional interface type and the expression is congruent ..." with T, in the case under discussion.

The definition of congruent specifies that where the function type's result is non-void, that the lambda must return a type compatible with the function type's result.

Additionally, it feels inconsistent / unintuitive that "(String s) -> s.trim()" is fine, but "s -> s.trim()" is not.

            Chris.


From: Maurizio Cimadamore [mailto:maurizio.cimadamore at oracle.com]
Sent: 16 August 2016 13:49
To: Greenaway, Chris; compiler-dev at openjdk.java.net
Subject: Re: Lambda return types and overloaded methods




On 16/08/16 13:38, Greenaway, Chris wrote:
That doesn't seem to fit with the discussion on this JDK bug: https://bugs.openjdk.java.net/browse/JDK-8029718 which has been fixed. In particular, there's a link to a discussion on this mailing list ( http://mail.openjdk.java.net/pipermail/lambda-dev/2013-November/011394.html ) where various members express the opinion that the compiler should consider more than just arity.
The bug (and the discussion) you refer to consider the case of value vs. void compatible lambda. I.e. if a lambda does not return, that information is enough to rule out a functional interface with non-void return type.
In your case the lambda is returning something, and both functional interfaces also return something. So that doesn't apply.


An important consideration seems to be 15.27.3 of the JLS. Paraphrasing, it says that a lambda with implicitly typed parameters is only compatible with a functional interface if the lambda body results in a value compatible with the functional interface's result type. The definition of potentially compatible in 15.12.2.1 specifically references this.
I don't see references from 15.12.2.1 to 15.27.3. I see however references to 15.27.2 - when the potentially compatible definition takes into account as whether the lambda is void- or value- compatible.

Maurizio


            Chris.



From: Maurizio Cimadamore [mailto:maurizio.cimadamore at oracle.com]
Sent: 16 August 2016 11:18
To: Greenaway, Chris; compiler-dev at openjdk.java.net<mailto:compiler-dev at openjdk.java.net>
Subject: Re: Lambda return types and overloaded methods


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


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


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/5d4d6544/attachment-0001.html>


More information about the compiler-dev mailing list