I'm torn on this. I prefer explicit types in declarations as well, but very much like the convenience of inferred types. I find myself thinking, though, that the demand for the convenience of inferred types in these languages is a result of the verbosity in the type specifications themselves, usually with generics (Rust) or templates (C++). I rarely use the convenience of inferred types for concise type labels (e.g. non-generic structs, integers, etc.). Maybe this is a deficiency in the languages' designs?
We put a lot of consideration into this. Basically, we could let you elide types everywhere, but didn't. There's a few reasons why, and they're sorta inter-related.
If you let type signatures be inferred, then you can get very poor errors. This happens when you change some code in the body, that changes the type signature of the function, which then causes an error to happen where the function is used. This misdirects from the source of the error, rather than helping you figure out what goes wrong.
Conceptually, signatures are the place where contracts are enforced, especially in Rust. Everything about a function should be expressed in its signature, so that way we can do analysis based on only the signatures. This helps in a few different ways: it's faster for the compiler, and it's also easier for the human reading it.
The bodies of functions, however, aren't exposed to the greater world. With a defined signature, the compiler can give you great error messages, pointing exactly to where you made a mistake. In this case, inference makes sense.
It's not just about convenience though. You can see this in languages like C++, which optionally support type deduction (but not full inference):
auto a = 1 + 2;
int a = 1 + 2;
With auto, the type is deduced. Without auto, you specify the type.
There are various arguments that range between "never auto unless you have to" and "always auto".
SomeType<OtherType>::SomeOtherType * pObject = new SomeType<OtherType>::SomeOtherType();
auto pObject = new SomeType<OtherType>::SomeOtherType();
Here, auto improves readability, because you end up just repeating the type over again. This was my main frustration with Java's generics back in the day. (I primarily used Java in the 1.4 ~ 1.5 era, I know today's is a very different beast!)
This is directly related to the parent post; some might say "explicit is better than implicit" but in my opinion, while that's often true, implicit is better than explicit sometimes. The above is a great example. The post is all about trying to tease out better words to talk about these differences.
I think there is a lot of useful information, within a function body, that can be created by inclusion of specific types. I don't think your decision was bad. People obviously like the language and the compiler's type inference systems are apparently extremely advanced.
In my opinion this is an anti-pattern for beginning developers and complicates the development of IDEs. This is for a number of reasons.
1. Saving screen space is not a good reason to omit types
Long long ago the people typing away at Bell Labs made these same decisions. All across the UNIX kernel and user space they used partial/shortened names. This did not help users nor did it help developers. K.T. is said to have joked that the missing "e" from "ceat" is his biggest regret from the UNIX system.
We don't use 80x40 terminal displays and most of us type at faster than 25wpm. The space or time saved when writing...
let people = new List<String>();
When compared to...
List<String> people = new List<String>();
Is not worth it in my opinion. This is even more so when you have situations like this
List<String> people = getGuestList();
vs
let people = getGuestList();
In my opinion this is considerably more difficult to understand what type I'm working with. I have to now go and read the header for `getGuestList()` to find out what I can do with my `people` variable.
2. IDE development will be more difficult.
This is minor, and will eventually be a solved problem, but writing an IDE that doesn't have immediate type information for everything in scope will be more complicated. Some may even say this might be impossible to accomplish via static analysis. Because of this the development of things like the RLS are much more difficult.
3. The seasoned but new developer will struggle to find how things work.
When I pick up a new programming language at this stage in my career I no longer read through a book or documentation. I sit down and I try and accomplish a project. I find a task, find some code that does the subsets of the problems I want to solve, read that code, then implement my own solutions with the knowledge that I've gained. With Rust this approach doesn't work (for me). I have not been able to pick up Rust. I don't know what kind of objects are being used to hold which kind of data without reading almost every single line of code in the program (or at least everything accessible from the scope I am interested in).
In Java, C, etc I get some clue as to what's going on. In Java I need to only read maybe the first few words on every line to get an idea of what will happen or what is being used.
public boolean isOnGuestList(String person) {
List<String> people = ...;
List<String> plusOnes = ...;
return people.contains(person) || plusOnes.contains(person);
}
I might not know what a List<String> is but I can easily find out.
This biggest effect this has is that since I have been programming for a long time I have an intuition where certain language features will be used. Every* language has a KV store, a list, some way to interact with file descriptors, etc. I know what kinds of programs will do those things and I can look at those programs for reference. Unfortunately for languages like Rust I need to already know the general types that people use to understand the code. All I would see is...
fn isOnGuestList(person: string) -> string {
let people = ...;
let plusOnes = ...;
people.contains(person) || plusOnes.contains(person);
}
I hear you. A lot of this is based on opinion; you find the duplication good, I find it near-unbearable. :)
One little objective thing though:
> but writing an IDE that doesn't have immediate type information for everything in scope will be more complicated. Some may even say this might be impossible to accomplish via static analysis. Because of this the development of things like the RLS are much more difficult.
The compiler is static analysis. If the compiler can figure it out, then so can any other tool. And in fact, the RLS works by asking the compiler directly, so it's not actually any harder. Well, there has been some refactoring needed within the compiler to support this, but it's the same interface as incremental re-compilation (which needs to ask the same questions) so it was gonna happen anyway.
I do think that IDEs are a way to split the difference here; you can hover over people or plusOnes and see the type.
> I hear you. A lot of this is based on opinion; you find the duplication good, I find it near-unbearable. :)
I don't really find it good. I just don't see any easier to follow alternatives. Imagine getting a contract to add a new feature to C code written for an old mainframe in the 80s. It's a pain, right? Now imagine you just deleted half of the type annotations and replacing them with let. Do you think that would be easier to read? Maybe a better middle ground is left-justified definitions of types? I don't know.
I think that
vec<int> something = new;
might be better than
let something = new vec<int>;
We read left to right, the first thing on the line is the left most item, and the left side of the variable definition with always be visible (can't have a function call on the left hand side).
> If the compiler can figure it out, then so can any other tool
I understand that but most IDEs, outside of the ones supporting the new language server initiative, don't really talk to the runtime/compiler. It's also the case that architecturing your compiler/IDE this way is very computationally expensive. I'm on an x220 which is only a few years old and RLS locks up every time I try to load a non-trivial project.
> I do think that IDEs are a way to split the difference here; you can hover over people or plusOnes and see the type.
It would help but I have not had any luck getting a decent IDE setup for Rust. I know this is something that I am not alone on and (thankfully) this is a criticism the Rust community is very receptive to. I'd be really interested in hearing what you and many of the other core devs do for your environments.
I don't think this is right at all. The name of the variable is far more important than its type - ideally, the name of the variable should include a hint as to the nature of its type, whether it's explicit (e.g. parent and child in a tree navigation function are obviously references to the tree node type that is being navigated over) or very lightly suggested (e.g. pluralization, like children for a list of child things). The actual type is of secondary importance, chosen for implementation reasons (e.g. a map might be hash-based or tree-based, or a list might be a view over some other list, synthesized from some foreign collection, or a wrapper around an array in memory).
For me, the name of the variable is of paramount importance for understanding the code. The type is very secondary. Types are usually in two buckets: domain-specific (so the name typically is highly relevant to what's being done and is often the same as the type name or an abbreviation or agglutination), or common denominator library types (typically collections, strings, primitives, that kind of thing) where the name of the variable implies the type.
The name is the thing that you see over and over again in the code. You also don't get to see the type when you call a method. So readable code needs good consistent names that make type annotations helpful but far from mandatory for comprehension.
I use VS: Code with the RLS and the plugin we provide for it, that's it. I also sometimes use vim with syntax highlighting, but no RLS or anything fancy.
> RLS locks up every time I try to load a non-trivial project.
I wonder if this is the initial build; that is, right now, RLS basically needs to build your code in order to do its thing, so the first time you open stuff, it's gonna be slow and make your fans whir. After that it shouldn't be a huge deal.
Things have been a bit wonky as of late, but the RLS is now riding the trains at least, so being available on stable will be very nice.
> We don't use 80x40 terminal displays and most of us type at faster than 25wpm.
But reading and understanding code is still the most time-consuming part of programming, and when a function or class stops fitting on a single screen that's a huge hit to readability, whether that's 40 lines or 100.
> have to now go and read the header for `getGuestList()` to find out what I can do with my `people` variable.
No you don't, you just mouseover it or press a simple key shortcut and get it.
> This is minor, and will eventually be a solved problem, but writing an IDE that doesn't have immediate type information for everything in scope will be more complicated. Some may even say this might be impossible to accomplish via static analysis.
Huh? The IDE already has to do all the work anyway because it already has to compile and check the code. It's no harder for the IDE to use the inferred types, assuming the compiler was suitably modular.
> When I pick up a new programming language at this stage in my career I no longer read through a book or documentation. I sit down and I try and accomplish a project. I find a task, find some code that does the subsets of the problems I want to solve, read that code, then implement my own solutions with the knowledge that I've gained. With Rust this approach doesn't work (for me). I have not been able to pick up Rust. I don't know what kind of objects are being used to hold which kind of data without reading almost every single line of code in the program (or at least everything accessible from the scope I am interested in).
Switching from an Algol-family language to an ML-family language is a bigger change than switching between two Algol-family languages. It's going to be more work. But improvements are always changes.
> Unfortunately for languages like Rust I need to already know the general types that people use to understand the code. All I would see is...
Why's that a problem? Particularly since you said you already know Python, isn't that a perfectly sensible bit of code to read? Write what you would in Python, but get faster feedback on whether it's right (compile failure rather than runtime error) - isn't that something worthwhile?