Lambda return types and overloaded methods
Maurizio Cimadamore
maurizio.cimadamore at oracle.com
Tue Aug 16 12:49:06 UTC 2016
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
> *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* /
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.openjdk.java.net/pipermail/compiler-dev/attachments/20160816/01041010/attachment-0001.html>
More information about the compiler-dev
mailing list