Lambda and method reference inference
Dan Smith
daniel.smith at oracle.com
Fri Dec 7 14:10:34 PST 2012
On Dec 6, 2012, at 11:56 AM, Remi Forax <forax at univ-mlv.fr> wrote:
> I think i've already raised this point in August during the face to face meeting and at that time nobody care, maybe this time, I will have more chance :)
>
> with
> interface A<T> {
> T foo(T a, T a2);
> }
>
> this compile:
> A<Integer> b = Integer::plus; // with plus defined as int plus(int,int)
>
> but not this one:
> A<Integer> c = (int x, int y) -> x + y;
>
>
> Can we, please, have the same rules for both method references and lambdas that have their type of parameters specified ?
For what it's worth, I have been hanging on to this idea as something to circle back on.
It would be useful if you could expand on your motivation. I'm guessing it is one (or both) of:
1) You don't like the model we have and prefer yours.
2) You want direct control over the signature of the method generated by the compiler, for performance reasons.
Where we left this last is that we like the model ("we" meaning Oracle folks, plus I haven't heard much opposition from other EG members), but it's something to reconsider if we find that performant code requires some manual intervention for tuning (e.g., minimizing boxing).
Also, it would be useful to define the scope of what you're after:
1) Parameter types can be boxed/unboxed
2) Parameter types can be widened (or anything else legal in an invocation context)
2') Thus, type parameters for generic descriptors can be widened to their bounds
3) Parameter types can be grouped via varargs
Consistency with method references would suggest doing all of these...
---
Addressing the model: Brian summarized the model succinctly—lambdas _override_ a functional interface method; method references are _invocations from_ a functional interface method. In other words, this:
Integer::plus
is shorthand for this:
(x,y) -> Integer.plus(x, y)
If the types of 'x' and 'y' are 'Integer', that's fine. It is not necessary that 'x' and 'y' have type 'int'.
You point out that certain adaptations on the parameters can't be performed, and you have to do that yourself inside the body (e.g., cast Integer to int or call a method that will do the conversion automatically). In the rare circumstance in which such conversions have semantic consequences, that's true enough.
We have a pretty good story for widening where the target type uses wildcards. For example:
Predicate<? super Integer> p = (Number n) -> ...; // legal
Here, we infer that the instantiation of Predicate is Predicate<Number>.
So probably the main practical impact of the problem, if there is one, is with respect to boxing/unboxing. If we eventually support primitives as type arguments, that problem may go away, too...
Brian points out that equality is more useful for inference than convertibility. For example, the above wildcard inference problem essentially tries to find an alpha such that Predicate<alpha> is a valid target type. With equality constraints, this means, trivially:
alpha = Number
(More generally, we could have something like a descriptor parameter type Foo<? super Bar<alpha>> and a lambda parameter type Foo<? super Bar<Monkey[]>>; it's always just pattern matching to solve for alpha.)
In contrast, with convertibility constraints, we get
alpha :> Number
And then we need to work out whether there are other constraints on alpha (e.g., the declared bounds), and there's a bunch of complexity to solve for the "best" alpha and hope we're right. Of course we know how to do this, because that's how we handle method invocations, but in the context of deciding what the descriptor of a target type is, it's sure nice to keep things simple.
Similarly, we looked into contravariance for the most-specific algorithm. If you look at my write-up about overload resolution from a few months ago (jsr-335-eg list, subject "Method invocations & applicability", subheading "Functional Interface Parameter Types"), you'll see discussion about how both contravariance and covariance are problematic. So we settled on requiring that the parameter types be the same. Turns out, that was a really good choice. Because if the parameter types are different, we can't make any guarantees about the interpretation of the body. As we've explored the "most specific" analysis and decided, e.g., that we want to know whether certain lambda returns produce primitives or references, it's really nice to have a stable interpretation of the body with which to work.
Summarizing, the idea that parameter types should match with something else pops up quite a bit in our design, and typically frees up the compiler to do more useful things. It's not just an anomaly in the lambda compatibility rules.
—Dan
More information about the lambda-spec-observers
mailing list