78

It is allowed to assign var in Java 10 with a string like:

var foo = "boo";

While it is not allowed to assign it with a lambda expression such as:

var predicateVar = apple -> apple.getColor().equals("red");

Why can't it infer a lambda or method reference type when it can infer the rest like String, ArrayList, user class, etc.?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
hi.nitish
  • 2,732
  • 2
  • 14
  • 21
  • 14
    Which specific type `var` should infer? One of standard functional interfaces or one of your own? On what that decision should be based? – Pshemo Mar 30 '18 at 17:18
  • I think "var" is inferred to the most concrete type. When we assign var myList = new ArrayList(); It assigns Arraylist as compile time reference and not the List. Similarly, a default rule could be set. – hi.nitish Mar 30 '18 at 17:27
  • Which in this case should be...? What makes you think so? – Pshemo Mar 30 '18 at 17:27
  • @Pshemo I'd expect `Function` (assuming `apple` is of type `Fruit`). – lexicore Mar 30 '18 at 17:33
  • 10
    @lexicore And I'd expect `Predicate` (assuming `apple` is of type `Apple`). And that's why this is a bug, not a feature. – erickson Mar 30 '18 at 17:35
  • @erickson but the purpose of var is to increase coding speed and readability. It does not increase performance, does it? https://docs.oracle.com/javase/10/language/toc.htm#JSLAN-GUID-B06D7006-D9F4-42F8-AD21-BF861747EDCF – hi.nitish Mar 30 '18 at 17:47
  • 7
    I never implied anything about performance, and I'm not sure why you asked that. The point of my comment is that you can't infer the type of your variable from its initializer; it's ambiguous, as I've demonstrated. – erickson Mar 30 '18 at 17:49
  • @erickson that is my point, it can be inferred from the declared method where it is referred, if it is not referred then it is dead code. apples.stream.filter(predicateVar); When the final goal is to increase coding speed. Though i agree with your point as quite logical. but think from the other way, It is like opening a door called var but partially. – hi.nitish Mar 30 '18 at 18:04
  • 3
    You’re right it would have been possible for the language designers to have allowed the type of predicateVar to be inferred from the way it is used. However that would have made coding more difficult - the meaning of a line of code could only be understood by looking further down the screen. – cpp beginner Mar 30 '18 at 18:12
  • 3
    Yes, in some cases, it would be possible to infer its type from its *usage*, but that's not how it works. It *appears* to be do-able, but there are some obvious complications; at the very least, the compiler (and a human reader) has to look beyond the initializer. And if the `var` is referenced in different places with different types, what type is inferred? And those are just first-glance doubts. Stuart Marks and other have commented that in implementing lambda support, things that looked possible at first later turned out to be impossible. – erickson Mar 30 '18 at 18:13
  • 5
    Coding speed is a minor consideration. 90% of your time will be spent reading and maintaining existing code, not writing new code. As [a VP of Java development at Sun once put it,](https://web.archive.org/web/20071010144846/https://weblogs.java.net/blog/kgh/archive/2004/09/evolving_the_ja.html) “It is more important that Java programs be easy to *read* than to *write.”* – VGR Mar 30 '18 at 18:14
  • @erickson that is my point, it can be inferred from the declared method where it is referred, if it is not referred then it is dead code. apples.stream.filter(predicateVar); When the final goal is to increase coding speed. Though i agree with your point as quite logical. but think from the other way, It is like opening a door called var but partially. what if, it is referred both way? var list = new ArrayList();, now writing list=new LinkedList() gives error. we use final keyword this way as well, either through constructor or directly.. many arguments in the debate. – hi.nitish Mar 30 '18 at 18:14
  • .. and btw, var is always local, http://openjdk.java.net/jeps/286 – hi.nitish Mar 30 '18 at 18:16
  • 3
    If you see this as a debate, this question is primarily opinion-based, and as such, off topic. We can only explain why the language designers made the choice they did, which has been done well by in several answers as well as the comments here. If you are really protesting that your notion of the feature is better, this question should be closed. – erickson Mar 30 '18 at 18:37
  • 4
    Related question with excellent answers - https://stackoverflow.com/questions/49134118/array-initializer-needs-an-explicit-target-type-why – Oleksandr Pyrohov Mar 30 '18 at 18:43
  • 1
    @Oleksandr Precisely. Goetz has the answer. – hi.nitish Mar 30 '18 at 18:53
  • @erickson please do not get agitated, we should always explore the possibilities of have and have nots. your answer is also correct. – hi.nitish Mar 30 '18 at 18:58
  • 3
    @hi.nitish Scala's type system is different from Java's. For one, Scala has structural function types; Java made the deliberate choice to go with nominal function types. The inability to guess which functional interface you want is a consequence of this choice. This has nothing to do with `var`. – Brian Goetz Mar 31 '18 at 01:23
  • 3
    @hi.nitish What you propose can be done (e.g. look at Haskell). However allowing powerful type inference has a number of consequences: 1) way harder to implement the type inferencer correctly 2) Usually type inference becomes undecidable *fast*, so you'll always have well typed programs that you cannot infer (and people that tell you how you could make *that specific example* work, even though there are an infinite number of special cases to handle...) 3) Type errors become confusing for somebody that does not know the language really well 4) Compilation time might increase. – Bakuriu Mar 31 '18 at 07:47
  • 2
    @hi.nitish Java, C++ and other languages that are used a lot in the industry have to deal with many programmers that would be hindered by confusing errors ecc, so they preferred to all only a minimal form of type inference that does not require any "lookahead". Surely, this also means that it can only make "dump" type inferences. They traded in favor of usability for not-so-well educated programmers. – Bakuriu Mar 31 '18 at 07:49
  • @Bakuriu Exactly, I just got a wild rational thought while reading about var and I wanted to find whether it is feasible or not, howsoever complex it may be. The greatest thing that happened is Goetz answered. Thanks. – hi.nitish Mar 31 '18 at 10:39
  • 1
    C#, which has had `var` for over ten years, has this same restriction. – Ryan Lundy Apr 08 '18 at 09:40

7 Answers7

75

This has nothing to do with var. It has to do with whether a lambda has a standalone type. The way var works is that it computes the standalone type of the initializer on the RHS, and infers that.

Since their introduction in Java 8, lambda expressions and method references have no standalone type -- they require a target type, which must be a functional interface.

If you try:

Object o = (String s) -> s.length();

you also get a type error, because the compiler has no idea what functional interface you intend to convert the lambda to.

Asking for inference with var just makes it harder, but since the easier question can't be answered, the harder one cannot either.

Note that you could provide a target type by other means (such as a cast) and then it would work:

var x = (Predicate<String>) s -> s.isEmpty();

because now the RHS has a standalone type. But you are better off providing the target type by giving x a manifest type.

Brian Goetz
  • 90,105
  • 23
  • 150
  • 161
64

From the Local-Variable Type Inference JEP:

The inference process, substantially, just gives the variable the type of its initializer expression. Some subtleties:

  • 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.

Because a lambda expression by itself does not have a type, it can not be inferred for var.


... Similarly, a default rule could be set.

Sure, you can come up with a way to work around this limitation. Why the developers made the decision not to do that is really up to speculation, unless someone who was part of the decision making can answer here. (Update: answered here.) If you're interested anyway, you could ask about it on one of the openjdk mailing lists: http://mail.openjdk.java.net/mailman/listinfo

If I were to guess, they probably didn't want to tie lambda inference in the context of var to a specific set of functional interface types, which would exclude any third party functional interface types. A better solution would be to infer a generic function type (i.e. (Apple) -> boolean) that can than be converted to a compatible functional interface type. But the JVM does not have such function types, and the decision to not implement them was already made during the project that created lambda expressions. Again if you're interested in concrete reasons, ask the devs.

Community
  • 1
  • 1
Jorn Vernee
  • 31,735
  • 4
  • 76
  • 93
  • 3
    taking into consideration how little optimizations are done by `javac` *in general* doing a potential search for an implementation a poly expression at compile time would be insane I guess – Eugene Mar 30 '18 at 18:42
  • alos, no idea why you deleted the other answer on class ladder issue, IMO it was the correct one – Eugene Mar 30 '18 at 18:47
  • @Eugene My idea about that is that searching for an implementation at compile time is not really in line with the general dynamic linking/loading philosophy of the JVM. So it just doesn't seem like a very _nice_ solution. – Jorn Vernee Mar 30 '18 at 19:29
  • @Eugene I deleted that other answer because the OP said the solution I gave didn't work for container environments. I don't really use containers ever, so I just assumed I made a mistake and deleted the answer. (I'll undelete it again then). – Jorn Vernee Mar 30 '18 at 19:32
  • 14
    Searching for a compatible functional interface is not a matter of performance; it's a matter of mind reading. There are multiple functional interfaces in the JDK alone compatible with the example; how would you expect the compiler to pick which one you had in mind? – Brian Goetz Mar 31 '18 at 01:21
  • 1
    @BrianGoetz You don't. The compiler defines a list of types (e.g. the ones from `java.util.function`, maybe re-ordered a bit), and picks the first one in the list that's compatible, or throws an error if none are. So you basically let the compiler-writer decide for you (I assume that's what's meant by 'default rule' in the quote.). Not really a good idea imho since any reader of the code would have to memorise the exact system to know which type was picked. – Jorn Vernee Mar 31 '18 at 16:43
  • 4
    @JornVernee You missed my point, in two ways. 1. `(String s) -> s.isEmpty()` is already compatible with both `Function` and `Predicate`, among others. Asking to choose is asking for mind-reading. And if we added new ones, then that might cause the types in people's programs to change, or existing programs to no longer compile. Bad idea. 2. The functional interfaces in `java.util.function` are not magic or part of the language; they're just ordinary interfaces. Much better for the user to just say the type they mean. – Brian Goetz Mar 31 '18 at 18:35
  • 2
    @BrianGoetz Have I missed the point? We seem to be advocating for the same thing. The point I'm trying to make in the answer is that you can always come up with some sort of way to make it work, but that doesn't mean the solution is a _good_ one. But a question like "Why didn't they just do X" (I know you love getting those) can not really definitively be answered by the SO community in general, though we can speculate. – Jorn Vernee Mar 31 '18 at 18:59
35

To everyone who is saying this is impossible, undesired, or unwanted, I just want to point out that Scala can infer the lambda's type by specifying only the argument type:

val predicateVar = (apple: Apple) => apple.getColor().equals("red")

And in Haskell, because getColor would be a standalone function not attached to an object, and because it does full Hindley-Milner inference, you don't need to specify even the argument type:

predicateVar = \apple -> getColor apple == "red"

This is extraordinarily handy, because it's not the simple types that are annoying for programmers to explicitly specify, it's the more complex ones.

In other words, it's not a feature in Java 10. It's a limitation of their implementation and previous design choices.

Karl Bielefeldt
  • 47,314
  • 10
  • 60
  • 94
5

To answer this we have to go into details and understand what a lambda is and how it works.

First we should understand what a lambda is:

A lambda expression always implements a functional interface, so that when you have to supply a functional interface like Runnable, instead of having to create a whole new class that implements the interface, you can just use the lambda syntax to create a method that the functional interface requires. Keep in mind though that the lambda still has the type of the functional interface that it is implementing.

With that in mind, lets take this a step further:

This works great as in the case of Runnable, I can just create a new thread like this new Thread(()->{//put code to run here}); instead of creating a whole new object to implement the functional interface. This works since the compiler knows that Thread() takes an object of type Runnable, so it knows what type the lambda expression has to be.

However, in a case of assigning a lambda to a local variable, the compiler has no clue what functional interface this lambda is implementing so it can't infer what type var should be. Since maybe it's implementing a functional interface the user created or maybe it's the runnable interface, there is just no way to know.

This is why lambdas do not work with the var keyword.

SteelToe
  • 2,477
  • 1
  • 17
  • 28
  • 4
    I don't think that it's quite true that lambdas are "just syntactical sugar to implement a functional interface." In any case, that's not relevant to this otherwise nice answer. Consider rewording that paragraph. – erickson Mar 30 '18 at 18:04
5

As several people have already mentioned, what type should var infer and why should it?

The statement:

var predicateVar = apple -> apple.getColor().equals("red");

is ambiguous and there is no valid reason why the compiler should pick Function<Apple, Boolean> over Predicate<Apple> or vice versa assuming the apple identifier in the lambda represents an Apple isntance.

Another reason is that a lambda in its own doesn't have a speakable type hence there is no way for the compiler to infer it.

Also, "if this was possible" imagine the overhead as the compiler would have to go through all the functional interfaces and determine which functional interface is the most appropriate each time you assign a lambda to a var variable.

Ousmane D.
  • 54,915
  • 8
  • 91
  • 126
  • 1
    It can be inferred from the declared method where it is referred, if it is not referred then it is dead code. apples.stream.filter(predicateVar); When the final goal is to increase coding speed. Though i agree with your point as quite logical. but think from the other way, what if, it is referred both way? var list = new ArrayList();, now writing list=new LinkedList() gives error. we use final keyword this way as well, either through constructor or directly.. many arguments in the debate. .. and btw, var is always local, http://openjdk.java.net/jeps/286 – hi.nitish Mar 30 '18 at 18:20
  • 2
    the fact that compatible functional interface are not readily interchangeable is already a bigger problem, and var is just carrying that problem as well – njzk2 Apr 01 '18 at 05:35
2

Because that is a non-feature:

This treatment would be restricted to local variables with initializers, indexes in the enhanced for-loop, and locals declared in a traditional for-loop; it would not be available for method formals, constructor formals, method return types, fields, catch formals, or any other kind of variable declaration.

http://openjdk.java.net/jeps/286

0

In a nutshell, the types of a var and lambda expression both need inference, but in opposite way. The type of a var is inferred by the initializer:

var a = new Apple();

The type of a lambda expression is set by the context. The type expected by the context is called the target type, and is usually inferred by the declaration e.g.

// Variable assignment
Function<Integer, Integer> l = (n) -> 2 * n;
// Method argument 
List<Integer> map(List<Integer> list, Function<Integer, Integer> fn){
    //...
}
map(List.of(1, 2, 3), (n) -> 2 * n);
// Method return 
Function<Integer, Integer> foo(boolean flag){
    //...
    return (n) -> 2 * n;
}

So when a var and lambda expression are used together, the type of the former needs to be inferred by the latter while the type of the latter needs to be inferred by the former.

var a = (n) -> 2 * n;

The root of this dilemma is Java cannot decide the type of a lambda expression uniquely, which is further caused by Java's nominal instead of structural type system. That is, two types with identical structures but different names are not deemed as the same, e.g.

class A{
    public int count;
    int value(){
        return count;
    }
}

class B{
    public int count;
    int value(){
        return count;
    }
}

Function<Integer, Boolean>
Predicate<Integer>