Lambda return types and overloaded methods
Maurizio Cimadamore
maurizio.cimadamore at oracle.com
Tue Aug 16 16:41:59 UTC 2016
On 16/08/16 14:58, Greenaway, Chris wrote:
>
> 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.
>
I think (the spec gurus will correct me if I'm wrong) that 15.27.3 only
happens when you look inside the body of a lambda (*). Whether you are
allowed or not to look inside the body is dictated by 15.12.2.1.
(*) is not even that simple, as 15.27.3 is used only when the target
type of a lambda is 'ground' i.e. w/o inference variables - if it has
inference variables slight variation of 15.27.3 is used, namely 18.2.1.
Maurizio
>
> 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/3857ee18/attachment-0001.html>
More information about the compiler-dev
mailing list