JEP 286: Local-Variable Type Inference
openjdk.java.net
openjdk.java.net
The website isn't the prettiest but Lombok is a fantastic addition to a Java codebase and massively reduces the amount of boiler plate often associated with Java. A list of Lombok features https://projectlombok.org/features/index.html
Java 8 + Lombok + Guava == happy developers (well at least in my case :) ).
[0] https://gist.github.com/seanjensengrey/a08910b0d204b037c2ba
A while back, I was trying to explain in what ways Scala is more typesafe than Java for a friend (which was one argument to why I prefer Scala so much), and ended up realizing how important type inference is in this regard.
The reason was this: quite often type signatures correctly modelling a domain ends up being pretty hard to read. In a good and proper API, this happens in particular whenever something is composable and is, in fact, composed (if one can say that). I suppose one can say that, for Scala, it took a bit of time before people figured out how to do this properly, rather than just hacking the type system, but I feel that the ecosystem is more or less there now... YMMV though...
As an example: in something like Slick (a functional-relational mapper) in Scala you end up with monads to model your SQL query. One advantage this has is compile errors whenever you're doing something wrong (i.e. more typesafe), but the types that a SQL query representation ends up with will necessarily be pretty hard to read and practically impossible to write (you could have a Query of a long chain of other types for example if you're querying multiple columns). In this example, somebody writing the query might not care about the type of the query. They will care about what the query will end up returning when executed though. I suppose it will lead to trouble when you can't read the type signatures well enough and try to figure out the compiler error, but I reckon it's better to have an error, than to see it fail runtime (at least in my world :). It's easier and safer to do maintenance too, since the compiler and IDEs can know how things should work.
In any case, you could conclude that, in a statically compiled language where you do not have type inference, you'll end up trading in preciseness in the model of the domain, in order to have readable code from the callsite. In turn, this leads to less typesafe code.
One challenge that Java will have even when implementing this though, is how to move the entire ecosystem to something that is indeed more typesafe. This only works if the APIs are doing this right.
EDIT: typos, language and less parens
Type inference has absolutely nothing to do with type safety. Programming languages don't become more or less statically type safe depending on whether they support type inference.
Type inference only enables to omit type annotations in certain parts of your code. Your values are just as typed as if you had used that type annotation.
As for the original point you were trying to make, I would say Scala is no more type safe than Java. They are about on par in how they allow unsafe expressions to type check. Scala's type system is certainly richer than Java's, but that's a separate point (and maybe the one you were trying to make).
> In any case, you could conclude that, in a statically compiled language where you do not have type inference, you'll end up trading in preciseness in the model of the domain, in order to have readable code from the callsite. In turn, this leads to less typesafe code.
Absolutely not.
Omitting a type in your source doesn't remove the type from that variable. The code is exactly as typed with that type annotation as it is without.
This makes no sense.
The basic idea is that type inference would shift the sweet spot for the right amount of typing upwards.
The problem I tried to describe though is how feasible/typical/normal? it is to catch more or less (type) errors on compile time... Like here, where I basically my point is that it is too hard to use Java's type system to it's full extent because it becomes too cumbersome to actually use it on the callsite.
In (some) Java _programs_ you would circumvent modelling your domain correctly because it would be too cumbersome to use, therefore Java _programs_ tends to be less typesafe... I suppose that is how I use the term and heard it used in my everyday life, e.g. 'this code is more typesafe than that' and so on and so on...
I guess I should have made it more clear that this how I used the word 'typesafe' (though your definition of it is more correct) and underlined that I meant Scala/Java _programs_ not the language (nor the type system) itself.
Still, it is correct that type inference (rather obviously) doesn't have anything to do with type systems.
EDIT: language, clarity
Collection<String> getNames() { ... }
and in your code you do
var names = getNames(); if (...) names = Collections.emptySet();
Now, if the api later changes getNames() to be
List<String> getNames() { ... }
your code breaks. That's one of the advantages of only allowing this on final variables (besides reusing an existing keyword): it means this can't happen. Of course you already have a problem if the class has subclasses but I would argue that this is more subtle and problematic.
var list = new ArrayList<>();
There is no way to infer the type of the generic parameter. So we will go back to doing: var list = new ArrayList<String>();
Which isn't a big deal IMO because the generic parameters had to be specified on the LHS anyway to use the diamond. You also end up using fewer characters in general with var even without being able to use the diamond.Regarding val vs let, I prefer let simply because it's harder to accidentally type let vs typing var instead of val (or vice versa).
This works perfectly fine in practice as can be seen using comparable constructs in C#, C++, and many other mainstream languages.
If anything it's more readable, since your code isn't littered with redundant type info.
I fail to see the redundant type info.
Example 1:
int maxWeight = blocks.stream() .filter(b -> b.getColor() == BLUE) .mapToInt(Block::getWeight) .max();
"int" is not redundant type info here.
Example 2:
List<String> list = new ArrayList<>(); // (1)
var list = new ArrayList<String>(); // (2)
Many would say List<String> in (1) is redundant but I would argue otherwise. The intention of List<String> in (1) is to reference to the underline ArrayList through List interface. (1) is trying to encourage programming to an interface while (2) just completely destroys the practice by inferring the type for list variable to be ArrayList<String>. That means subsequent method invocations after (2) can be methods from ArrayList not List.
So instead of doing this:
Foo foo = new Foo("bar");
You can do this: Foo foo = Foo.of("bar");If you were using a factory, var would infer the factory signature's type, which would normally be the interface.
So you think using functions is enterprisey and over-engineering things?
Actually, people left Java land because they couldn't express what they needed to without using complicated design patterns.
There is a point where typing becomes verbose and decreases readability. If the compiler can infer, it should. It'll save time, money, and sanity.
I like the approach mentioned of just reusing final for immutability.
Isn't that good enough?
The List<String> is almost as redundant as the RHS <String> was.
var stream = list.stream(); // infers Stream<String>
How would you write it in order to take advantage of type inference?In rust and haskell, ect. types can be worked out backwards so that the type is figure out with use.
fn main() {
let mut v = Vec::new();
v.push("hello");
v.push("world!");
println!("output: {:?}", v.join(" "));
}
the type of v is `Vec<&str>` worked out backwards from where &str literal is added(since the type of push is fn `push(&mut self, value: T)` the T generic of Vec<T> must be &str)."The identifier var will not be made into a keyword; instead it will be a reserved type name. This means that code that uses var as a variable, method, or package name will not be affected; code that uses var as a class or interface name will be affected (but these names violate the naming conventions.)"
I didn't know there were such things as "reserved type names", but I'm guessing `var` will be treated sort of like `String` or `Object` (always available without an explicit import) and maybe it'll be a class in `java.lang`?
Seems like a much "nicer" approach than when Enum types were introduced with the `enum` keyword, but I guess that was slightly different.
And it seems we were right to reduce the scope of it. PHP 7 prohibiting "String" as a class name ended up being a significant BC break affecting several projects. I imagine it might have been worse still if `string` was a reserved word.
`List<String> a = new ArrayList<String>();`
I know that for some things it is convenient, but I appreciate the reminder to treat interface and implementation as distinct concepts.
But i see where you come from, it feels a bit sloppy to not document the intention that the variable _could_ be any kind of list, even though it happens to be an ArrayList. Not "this may lead to nasty bugs"-sloppy, but a slight case of "this won't win any beauty contests"-sloppy.
If your method bodies are so large that you need to be coding to interfaces within them (as opposed to just setting the return type of your method to the interface), the you've got other problems.
This survey will remain open for one week, from Mar 9 to Mar 16.
Gavin King is famous for three things in the Java world: creating the leaky-abstraction-riddled ORM Hibernate that never quite behaves the way you want, creating the never-really-used DI lib Seam that is notorious for being difficult to test and configure (despite standardized "javax" APIs), and being a difficult personality to work with.
Multiple compilation targets are a downside when: - It means that as a user of the language I have to write different code because the collections/scoping on one target behave much differently than on another - It means that as a maintainer of the language, I have to try to make core language features work with multiple very-different compilation targets, making the codebase N times harder to maintain, and usually meaning either that the language stagnates for long periods or that one platform is treated as "primary" and the others are second-class citizens that trail behind (see Scala on the JVM vs Scala.js, Clojure vs Clojurescript, F# vs Websharper, etc).
But I'm glad it's just for initializers.
After playing with Scala, I've found out I don't love type inference as much as I thought I did. In Scala, the compiler can infer return types of named procedures for example, but I've always found omitting these makes my code harder to read, having to read the body (an implementation detail) to see what it may be returning. It would be even worse if types were optional for parameters (ala Haskell). Other modern languages (ala Rust) have come to realization that this is a bad idea.
The same even applies to having the compiler infer types from local variables to a lesser extent. A lot of my code goes through transformations and chains, and sometimes it's not obvious what the resulting type is (and what the code is doing) if the type is not specified.
Even looking at the simple example mentioned from the article in isolation:
var stream = list.stream();
I don't know what type stream is. Sure, I can make an educated guess that it's some kind of Stream, but then knowing it's a parameterized, and then knowing what kind of type it takes is even more challenging. I would have to look at the right hand side (again, an implementation detail), or may have to traverse up even further (eg: looking up how `list` is derived in this case) to figure out for sure. Or worse yet, look up documentation. Imagine if the right hand side was composed by calling a series of procedures, then you have to traverse all the way to the right to validate the type. In the end, you have a mental burden.The same issue applies with calling functions and assiging their result to a local variable you're initializing.
I'm happy to see type inference is just added for initializers. Whilst I haven't found writing 'Foo foo = new Foo()' that common in functional code, I see that being prevalent in Java at least. On the flip side though, if your language or use case (C++?) makes code harder to understand rather than easier by specifying types, then you may be wishing you had type inference :)
The Haskell community seems pretty well agreed that top level declarations should have type annotations (arguments and return values) in code that's going to be maintained. With -Wall, GHC will even give you warnings about top level declarations without types. No reason to require it for quick (~5 min) experimental hackary or one-off scripting - in that case, add it if it helps and omit it if it doesn't, and the ability to ask what the compiler thinks the type is can come in handy as well.
I think deciding what to allow and prohibit by default is an important design decision.
I don't think I disagree with that, but I don't think it was made wrong here. It's true that you "risk the chance" of reading unannotated code, but I find there are times when the code I need to write is clearer to me than the type it'll have, and being able to defer annotation past compilation is occasionally a big win and often a small one.
And in that worst case scenario of having to look at unannotated code, you can fall back to asking your tooling.
I often wish I was using a text editor or IDE that understood my code and could insert type annotations for me, maybe automatically, and maybe not choose to do so if the line of code would "look" more redundant (i.e: parsing variable name or constructor/initializer).
Rust is one language that, while has the capability of doing full type inference, deliberately chose not to (https://doc.rust-lang.org/book/functions.html):
> This is a deliberate design decision. While full-program inference is possible, languages which have it, like Haskell, often suggest that documenting your types explicitly is a best-practice. We agree that forcing functions to declare types while allowing for inference inside of function bodies is a wonderful sweet spot between full inference and no inference
But readability can easily matter less than other things over the course of one compile. This is why I think the best choice is demanding top level annotations for reasonably complete code (say, every commit) and not doing so before I can ask for the shape of something, or see that it outputs what I thought, or run my tests, or see what type errors I get elsewhere.
It might be marginally better to demand it by default and have a flag to disable the check. Demanding it always is more than marginally worse (which is not to say it's the end of the world - it's totally not).
Which is kind of why I wish for tooling outside the compiler to make the effort in specifying types (when it increases readability, maybe based on heuristics) very minimal. Wishful thinking.
[1] https://docs.spring.io/spring-framework/docs/current/spring-...
@Autowired is fundamentally about type-driven injection
If you replace:
@Autowired
FooDao fooDao;
with something like: @Autowired
var fooDao;
I'm pretty sure type-driven injection breaks.[1] https://docs.spring.io/spring/docs/current/spring-framework-...
For easy cases reading such code is OK (var path = "/usr/bin/whatever" - obvious), but with a more complex code it becomes awful.
I'm a C++ programmer, and quite often I have to dig through dozens of headers to find out what this magic "auto" really means.
I wish most of the xtend enhancements were integrated to java asap!
Java has been super slow to adopt things that are more or less the norm in other popular languages. Why bother at this point? Isn't Kotlin a pretty good sugared-up Java, anyway?
Lightbend (caretakers of Scala, Play Framework, Akka, Spark, Slick, SBT, and others) wisely announced their dedication to both Scala and Java. It's caused a big stir in the community, but it kind of seems like that should make everybody happy. They're two ecosystems geared to serious software, built on a solid platform, but with very different constituencies. Lightbend realized there's no point in pressuring people to adopt Scala. People will do it on their own, if they want to. But with strong support of both languages, they can address a very broad swath of the development world.
They are also quite useful for wiring up context (take Akka's ActorSystem, for example) at the declaration level, so that the bodies of your classes and functions only explicitly talk about your domain objects, rather than framework machinery.
They're also a big part of the power behind projects like Shapeless, which are quite useful for scrapping boilerplate.
Most controversially, they are also used to implement implicit type conversions. Modern idiomatic Scala discourages this, but it can sometimes be helpful.
final int chosenByFairDiceRoll = 4; // a constant
final long millis = System.currentTimeMillis(); // not a constant
When you say "values are something else", is it that you would consider chosenByFairDiceRoll a value, but not millis? I guess that you might have had more exposure to languages that use "const" instead of "final" than me...And every time I see 'var', I cringe because I don't know if they mean variable or variant. But 'val' is just usless. Variables evaluate to values, constants evaluate to values, expressions are values, first-class functions are values... Everything is val.
Some of those languages are better than each other, and they are much worse languages than Clojure, another JVM-hosted language you ignored.
Who's typing Java code all by hand these days, though? There various IDEs that save me all the typing.
It's also nice to be able to go the declaration of a variable to see exactly what type it is - that's mostly for code not written by myself, or old code that I do not remember. For local variable that's fine (I hope we won't infer types this way for method parameters), so I guess I like it there.
No, it doesn't. From TFA: "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."
Which is pretty much how C# does it.
I gather that it's common for functional languages to infer pretty much everything for you most of the time, which I think would actually be nice. But presumably that's either difficult or impossible in Java.
Reducing boilerplate mostly helps with reading code, not with writing it.
Some truth in that, it should be safe enough to assume that the lightweight editor crowd has long self-selected out of java. I often do write-time type inference by declaring anything as an int and then, at the end of the line, pressing whatever magic key that makes the IDE rewrite it to the actual type so that it compiles.
One of the syntax options in the JEP is "final name = RHS;". When i first saw that i could not understand why anybody in their right mind would even consider a partial solution like that. But now that i wrote about write-time type inference, it starts getting more and more attractive:
A tiny nudge towards immutability, natural "write-time type inference" that is correct until the first nonfinal access is written (IDEs already offer a "make nonfinal?" fix in that case) and nonfinal identifiers will often be a broader type than the RHS assignment anyway.
C# added implicitly typed local variables (and the var statement) to the language 9 years ago.
From Java LINQ Examples: https://github.com/mythz/java-linq-examples