poly-expression type inference pothole
John Rose
john.r.rose at oracle.com
Wed Jul 3 21:39:15 PDT 2013
Consider the following code, which is not legal:
> import java.util.function.Function;
> class Infer {
> static <T,R> Function<T,R> function(Function<T,R> f) { return f; }
> String r = function(Object::toString).apply(42); //BAD
> }
The code tries calls a function-consuming factory (which in this case is the identity) on a method reference, and then invoke a method on the created object. Hopefully, types are inferred from "String r" and "42" and sort out the ambiguities in "Object::toString".
The diagnostic says something opaque like this:
Infer.java:4: error: incompatible types: Cannot instantiate inference variables T because of an inference loop
(The compiler mentions "an inference loop" a lot, has a monomaniac vibe to it.)
I spent a few hours reading the spec[1] to see what the compiler might be complaining about.
[1] http://cr.openjdk.java.net/~dlsmith/jsr335-0.6.2.html#G
I noticed one thing in particular, which is that type inference applies to poly expressions, but not all method invocations are poly expressions.
(In the spec. I found no inference loops, just inference and loops. Maybe they were waiting until I left before they got together.)
In the above example, Function<T,R>.apply depends on type parameters from a poly function expression. Meanwhile apply has no parameters of its own, and so is not a generic method. This might mean that (#).apply(42) would not apply bounds on inference variables arising from (#).
To test this theory, I added a gratuitous type variable to Function.apply (to make it officially generic) and retried:
> class InferGenApply {
> interface Function2<T,R> { <T_ extends T> R apply2(T_ t); }
> static <T,R> Function2<T,R> function2(Function2<T,R> f) { return f; }
> String infix = function2(Object::toString).apply2(42); //OK
> }
The compiler accepted this code.
Probably I'm interacting with compiler bugs, and/or misreading the spec., but it also seems like there is a weakness in type inference which should be addressed.
In this case in particular, I suggest that x.m(y) should be considered a poly expression if x is a poly expression. Perhaps that will make fluent APIs using default methods as friendly to type inference as function-oriented APIs using static methods.
— John
P.S. If the example above is representative, the language favors type inference on method parameters, more than inference on class parameters depended on by methods. This would be a bad thing, since in the assignment x=y.m(z), the type of y should be inferred by constraints derived from x and z. The alternatives are unpleasantly verbose: x=((Cast)y).m(z) or x=staticM(y,z). Both alternatives are harder to read and write.
(The "staticM" version reads poorly because it is in Verb Subject Object order, which sounds wrong in most languages. Prefer SVO, I do.)
Adding default interface methods to the language makes things worse, since in many cases the default methods will be disfavored by type inference, causing designers to avoid default methods in favor of the "safer" static generic methods. APIs will be warped.
P.P.S. I have heard it said a few times that problems like this arise from interactions between overloading resolution and type inference. Being cheerfully ignorant of the complexities, I don't believe that applies in this case. You don't need a fully inferred type in order to find applicable methods in x.m(y), just a class for x. From there the inference "toolkit" allows method selection to proceed (18.5.1) even if the types of x and y contain unresolved inference variables.
More information about the lambda-dev
mailing list