daniel.smith at oracle.com
Tue Jul 16 15:08:05 PDT 2013
On Jul 15, 2013, at 2:33 PM, John Rose <john.r.rose at oracle.com> wrote:
> On Jul 15, 2013, at 8:53 AM, Marc Petit-Huguenin <marc at petit-huguenin.org> wrote:
>> I am trying to use a comparator on a Map.Entry<String, Integer> stream, and I
>> am not sure why this does not compile:
> IMO, the language and APIs (apart from the fine print of the inference rules) seem to promise that this sort of expression works in the naively coded form.
> By "this sort" I mean a combination of a fluent chain x.a(#).b(#) including behavior-adjusting sub-expressions, either x.a(#).b(f(g(#)) or even x.a(#).b(y.f().g(#)), where the # can contain type-inferred lambdas or MRs.
> Just under the surface of the language, the "fluent" idiom really means the first (receiver) argument x gets special syntactic treatment, allowing a chainable infix syntax x m (args) instead of the function-style prefix notation m(x, args). Under the hood, and after names are resolved, a chain x.a(#).b(#).c(#) is presented to the JVM about the same as if it were coded in prefix notation c(b(a(x,#),#),#). The API designer should have (IMO) a free choice between the two surface syntaxes. If this is to be true, then the enhanced type inference algorithm should treat the two syntaxes with parity. (Even though the scoping rules for a,b,c are very different.) If there is not to be parity, then we need to give rules of thumb on where the inferencer will give up (and/or make the error messages far more directive).
> Put simply, either idioms like the one Marc ran into should work routinely (without explicit type arguments), or else there has to be some very clear practical guidance to ordinary users when and where to write the explicit type arguments.
> I would hope that the inferencer can help the user in many cases like Marc's.
Without ruling out the possibility of enhancements that address situations like this, let me provide some general guidance/motivation for the status quo.
1) Certain generic methods have to-be-inferred type variables in their return types. These are special entities: invocations can be poly expressions -- using context to influence type inference. When you're analyzing inference, mentally highlight these methods, because they will get special treatment. The same goes for diamond class instance creation expressions.
2) In Java 7, the special treatment for these methods applied only when i) the invocation is in an assignment context, and ii) the arguments to the invocation don't provide any lower-bound information about the variables that appear in the return type. In Java 8, this is supported much more broadly: in any *assignment* or *invocation* context, we use the target type to influence inference. We even let multiple levels of invocation contexts push down a target type (e.g., "String s = id(id(id(someMethod())))", because all the variables are essentially solved at the same time.
3) A method invocation receiver is _not_ in an assignment or invocation context. Thus, there is no context to influence the typing, and the expression must be typed on its own. Why can't we just pretend that an instance method is actually a static method, thus giving the receiver a target type? Because method resolution needs a "type to search" as a prerequisite, and this is the type of the receiver. It is hard, and in some cases impossible, to do everything else in method resolution (searching for applicable methods, picking the most specific one), when we haven't even worked out what the type to search is yet. Implication: receivers are _not_ interchangeable with method arguments. (This isn't new: arguments can be boxed but receivers cannot; arguments can have constraints on their type variables -- see Collections.sort -- but receivers cannot.)
4) We could certainly do a better job with error messages.
TLDR: the status quo is that when you type a dot, the compiler has to completely type-check the stuff to the left before it can proceed. Inference can do a lot of cool tricks with context, but will not be influenced by "context" that occurs after the dot.
More information about the lambda-dev