I prefer type inference. It’s extra clutter that can often be abstracted away with good variable and method names. If it quacks the way I need it then that’s one less thing I need to hold context of in my head.
You should read the article, because it’s pretty much a direct rebuttal with justifications to this exact argument. You’ve really just re-stated what the article disputes.
Which isn’t to say you’re wrong, I’d just be interested in your response to the arguments.
The article doesn’t make a persuasive case at all. It immediately backs off by acknowledging that 99% of type inference is fine, because it’s really only complaining about function signature inference, which is an extreme case that only a few obscure ML variants like Ocaml and F# support.
It’s like saying all american movies are terrible, and then clarifying that you’ve only seen White Chicks
I don’t want to infer types from my code. I’d rather infer the code from the types. Types are the spec, they are small and low in expressiveness, code is big and has infinitely more degrees of freedom than types. The bug surface area is smaller with types.
So it makes sense to use the types (simple, terse, constrained) to generate the code (big, unconstrained, longer to write, bug-prone). Inferring types from code is like building a complex machine without plans, and then using an X-ray diffractometer to extract plans from the physical object.
This is the argument.
This comes back to a perennially forgotten/rediscovered fundamental truth about coding: It is much easier to write code than read code
This is immediately followed by the next part that in any sufficiently large organization, you spend more time reading code than writing code.
Put it all together? Fractional second gains in writing that have meaningful expenses when it comes to reading aren’t worth it once you’re operating at any kind of scale.
If you and your buddy are making little hobby projects. If you have a 3 person dev team. If you’re writing your own utility for personal use… I wouldn’t expect these features to become evident at that scale.
Again, it isn’t saying that it’s something intrinsically wrong, it’s just that there is a trade off and if you really think about it, under most professional environments it’s a net negative effect on efficiency.
My response to the article is that you’re sacrificing gains in language because some people use outdated tools. Code has more context than what is just written. Many times you can’t see things in the code unless you dig in, for example responses from a database or key value store, or literally any external api. Type inference in languages that have bad IDE support leads to a bad experience, hence the author’s views on ocaml. But in a language like Kotlin it’s absolutely wonderful. If needed you can provide context, but otherwise the types are always there, you can view them easily if you’re using a decent IDE, and type inference makes the code much more readable in the long run. I would say that a majority of the time, you do not care about the types in any application. You care about the data flow, so having a type system that protects you from mismatched types is much more important that requiring types to be specified.
Maybe I’m missing something:
Does type inference provide a practical benefit to you beyond saving you some keystrokes?
What tools do you use for code review? Do you do them in GitHub/gitlab/Bitbucket or are you pulling every code review directly into your IDE? How frequently do you do code reviews?
You know what really sucks?
When I have a method that returns a Foo
and like 300 places it gets called.
And then I change it to return an ever so slightly different Bar
Ah, now I need to go and update the code in 300 places cause the return type changed.
“Can’t you just sed
your codebase?”
Maybe, but it could cause some serious unintended dawizard
If it returned a “Foo”, whose structure changes in such a way as to requires changes in all places it was used…
That, sounds to me like a disaster you avoided by being helped (which is the polite way to describe developers not getting away with ignoring lazy and dangerous type conversion bugs) to fix each and every usage of it.
No, there’s countless ways code could be consuming a Foo or Bar and not care which.
Literally any form of serialization won’t care, for example.
Also you can change from a Foo to a Bar in a non breaking manner, where it’s name changed but the still have the same interface.
We’re talking about type inference, right?
If you have countless examples, I’d be happy to entertain others that have to do with type inference, because 1) serialisation is not related, 2) renaming in APIs is arguably, also not. Though, I cannot remember the last time my IDE wasn’t able to know, and do this for me.
The solution to this problem (and many others) is to use an IDE / editor which supports refactoring like that. Which is pretty much every IDE / editor unless you’re using some very obscure language I think.
I dont think external tooling should be a factor in deciding your language’s definition.
I’m just glad that type inference can improve this sort of situation a bit:
ConfigManager configManager = new ConfigManager();
Like many things… it depends.
Type inference is wonderful for prototyping and writing short lived tools - it is an unnecessary expense for mature projects with a large number of developers on it.
At my shop we have a concept of “one off” routes that are written to accomplish a specific task and intended to be run once. A few of these are run repeatedly but with the understanding that these routes are unmaintained and exempt from automated testing requirements (we’ve got a separate bucket for routes that are only rarely invoked but are complex enough and frequently enough used to get test coverage). For stuff like those one off scripts I’ll never block a PR for omitting typing - while I absolutely will in our regular codebase.
To me this is an argument for why Go should not add type inference to function/method declarations. Go is like Rust (I guess, I haven’t used Rust) - type inference works for declaring a variable (or const) and generic type parameters but not for type declarations, methods, functions, etc. I was in the “more inference is always better” camp but now I’m thinking Go has the perfect level of inference. Except for function literals/lambdas. I really want go to infer the argument and return types when I’m passing a function literal/lambda to a function.
The thing about Rust’s type inference that seems wild to anyone who hasn’t seen Hindley-Milner/ML style type systems before is that it’s “bidirectional” (in quotes because that’s not a proper type theory term as far as I know). The type of the left-side of an assignment can determine the type (and behavior!) of the right side. For instance, this is ambiguous:
let foo = [("a", 1), ("b", 2)].into_iter().collect();
The expression creates an iterator over the (letter, number)
pairs, and collect()
stores the elements in a newly created container. But which container type? Here are two valid variants:
let foo: Vec<_> = [("a", 1), ("b", 2)].into_iter().collect();
This creates a vector with items ("a", 1)
and ("b", 2)
.
let foo: HashMap<_, _> = [("a", 1), ("b", 2)].into_iter().collect();
This creates a mapping where "a"
and "b"
are keys, and 1
and 2
are the corresponding values.
Playground link in case you’d like to mess with this concept: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=76f999f4db600415643b0c58c19c69b7