27

Following JEP 286: Local-Variable Type Inference description

I am wondering, what the reason is for introducing such a restriction, as:

Main.java:199: error: cannot infer type for local variable k

    var k = { 1 , 2 };
        ^   
(array initializer needs an explicit target-type)

So for me logically it should be:

var k = {1, 2}; // Infers int[]
var l = {1, 2L, 3}; // Infers long[]

Because Java compiler can already infer properly the type of an array:

void decide() {
    arr(1, 2, 3);  // call  void arr(int ...arr)
    arr(1, 2L, 3); // call  void arr(long ...arr)
}

void arr(int ...arr) {
}

void arr(long ...arr) {
}

So what is the impediment?

Paradox
  • 4,602
  • 12
  • 44
  • 88
Andremoniy
  • 34,031
  • 20
  • 135
  • 241

2 Answers2

33

Every time we improve the reach of type inference in Java, we get a spate of "but you could also infer this too, why don't you?" (Or sometimes, less politely.)

Some general observations on designing type inference schemes:

  • Inference schemes will always have limits; there are always cases at the margin where we cannot infer an answer, or end up inferring something surprising. The harder we try to infer everything, the more likely we will infer surprising things. This is not always the best tradeoff.
  • It's easy to cherry-pick examples of "but surely you can infer in this case." But if such cases are very similar to other cases that do not have an obvious answer, we've just moved the problem around -- "why does it work for X but not Y where X and Y are both Z?"
  • An inference scheme can always be made to handle incremental cases, but there is almost always collateral damage, either in the form of getting a worse result in other cases, increased instability (where seemingly unrelated changes can change the inferred type), or more complexity. You don't want to optimize just for number of cases you can infer; you want to optimize also for an educated user's ability to predict what will work and what will not. Drawing simpler lines (e.g., don't bother to try to infer the type of array initializers) often is a win here.
  • Given that there are always limits, its often better to choose a smaller but better-defined target, because that simplifies the user model. (See related questions on "why can't I use type inference for the return type of private methods. The answer is we could have done this, but the result would be a more complicated user model for small expressive benefit. We call this "poor return-on-complexity.")
Brian Goetz
  • 90,105
  • 23
  • 150
  • 161
  • Though I am not entirely convinced, but I can't ask for more than receive an answer from the author of this JEP :-D Thanks – Andremoniy Mar 06 '18 at 19:40
23

From the mailing list platform-jep-discuss, message Reader Mail Bag for Thursday (Thu Mar 10 15:07:54 UTC 2016) by Brian Goetz:

  1. Why is it not possible to use var when the initializer is an array initializer, as in:

    var ints = { 1, 2, 3 }
    

The rule is: we derive the type of the variable by treating the initializer as a standalone expression, and deriving its type. However, array initializers, like lambdas and method refs, are poly expressions -- they need a target type in order to compute their type. So they are rejected.

Could we make this work? We probably could. But it would add a lot of complexity to the feature, for the benefit of a mostly corner case. We'd like for this to be a simple feature.

The short-hand array initializer takes its type information from the declaration, but as the declaration here is var it must be specified explicitly.

You will need to choose between:

var k = new int[]{ 1 , 2 };

or

int[] k = { 1 , 2 };

Allowing var k = { 1 , 2 } would change the semantics of something that is already syntactic sugar. In the case of int[] n = { 1, 2 } the type is determined by the declaration. If you allow var n = { 1, 2 } the type is suddenly determined by the initializer itself. This might lead to (easier to create) compiler bugs or ambiguities.

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
  • 5
    Presumably `var n = 1;` works and assumes `int`? If so, why would `var a = {1, 2};` not assume `int[]`? I, the programmer, could certainly specify something else if I needed it. – T.J. Crowder Mar 06 '18 at 15:27
  • 3
    @T.J.Crowder In Java 9 and earlier, it is possible to `double[] a = { 1, 2 }` so there the type of the array is also not inferred from the type of the numeric literals, and they have preserved that rule. But yes, you could extend the argument that if `var a = 1` infers int, then so should `var a = { 1, 2 }` infer `int[]`. But short of one of the designers of `var` adding his comments here, I think they chose the conservative route here. – Mark Rotteveel Mar 06 '18 at 15:29
  • @MarkRotteveel I appreciate your answer, but I strongly disagree. Please see my update in the question with the example. – Andremoniy Mar 06 '18 at 15:32
  • @Andremoniy I think you're moving your question even further into "primarily opinion-based" territory with that edit. – Mark Rotteveel Mar 06 '18 at 15:33
  • Why? Please any arguments. – Andremoniy Mar 06 '18 at 15:33
  • @MarkRotteveel there is already a similar functionality in the Java compiler and I don't see why it can be used for the local-variable type inference. Why is it opinion-based? – Andremoniy Mar 06 '18 at 15:34
  • 3
    @Andremoniy Because you're technically asking us to read the minds of the Java language designers (or wade through the discussion threads) to infer that. – Mark Rotteveel Mar 06 '18 at 15:37
  • 2
    Given the success criteria of the proposal, there's clearly a *specific* reason they had to not have it infer `int[]`. I think the question is what that specific reason was; presumably this is documented somewhere. Anyway, I'll step back, don't want to make a noisy comments section. :-) *(Edit: This was a reply to your earlier comment, not the one just above this comment. :-) )* – T.J. Crowder Mar 06 '18 at 15:37
  • 1
    @MarkRotteveel you can consider it as this. But actually: 1) I would expect that Brian Goetz comes here and gives his explanation ;-) 2) somebody else will provide strong argumentation why it is not possible to do. :-D – Andremoniy Mar 06 '18 at 15:39
  • @Andremoniy It' s an interesting question. The JEP does say _"The initializer has no target type (because we haven't inferred it yet). Poly expressions that require such a type, like lambdas, method references, and array initializers, will trigger an error."_, which would seem to put the reason as _"However, their use as the types of local variables will significantly increase their exposure, revealing compiler/specification bugs and forcing programmers to confront them more frequently."_ mentioned in the "non-denotable types". – Mark Rotteveel Mar 06 '18 at 15:44
  • @Andremoniy If I'd make more guesses, it is that they didn't want to modify the behavior of syntactic sugar where in `var n = { 1, 2 }` the type is derived from the initializer, while in `int[] n = { 1, 2 }` it is derived from the declaration, that might add complications to the compiler (eg it would be easy to introduce bugs like `double[] n = { 1, 2 }` yielding _"incompatible types: int[] cannot be converted to double[]"_) – Mark Rotteveel Mar 06 '18 at 15:50
  • Hm... `double[] n = { 1 , 2 }` is a perfectly valid code. What's the issue, I am sorry? – Andremoniy Mar 06 '18 at 15:52
  • @Andremoniy It is a hypothetical bug in the compiler because you'd now need to explicitly distinguish between having var + array initializer and having explicit declaration + array initializer. – Mark Rotteveel Mar 06 '18 at 15:53
  • To be honest I do not get what is the issue with this stuff. What's the difference? It should now distinguish between `var bytes = 1` and `int bytes = 1` (compare `var bytes = Files.readAllBytes(path);`). – Andremoniy Mar 06 '18 at 15:57
  • @Andremoniy Yes but in this case `1` is not syntactic sugar for an int, it is an int by specification, while in the pre-Java 10 situation, `{ 1, 2 }` **only** has meaning and type when combined with a declaration of type. They probably considered allowing that a bridge too far (maybe for now?). As always, Java language designers are usually conservative. – Mark Rotteveel Mar 06 '18 at 16:04
  • 3
    @Andremoniy In any case, I have found a relevant quote from Brian Goetz, and removed some of my own opinion from my answer. – Mark Rotteveel Mar 06 '18 at 16:09
  • 2
    *We probably could. But it would add a lot of complexity to the feature, for the benefit of a mostly corner case* - Haha. Okay, that makes sense ;) – Andremoniy Mar 06 '18 at 16:18
  • 4
    Note that the choice of using a manifest type on the left or a `new` expression on on the right is not unlike that of combining `var` with "diamond", in that there are several ways you can provide the needed type information, and you get to pick one. (Its not a perfect comparison becomes sometimes you can get away with both `var` and diamond, but the principle is the same: somehow, you must supply enough type information, in one form or another.) – Brian Goetz Mar 06 '18 at 19:30