Later, we adopted static analysis tools and code linters. This helped, but only so much could be done with the dynamically typed languages which were popular in the early internet.
As a self-taught programmer with no college degree, I started with dynamic languages: Perl, JavaScript, PHP, Ruby. I viewed the statically typed languages (and the programmers who could wield them) with reverence and awe. C, C++, Java, these were “real” languages that required big brains, and I should stay in my dynamically typed playpen. But as I matured, my understanding shifted: dynamic languages require hubris, require belief that you can keep an entire complicated codebase in your head with perfect recall. Statically typed languages, it turns out, invite humility.
With a static type system, each step in a program’s lifecycle can be a little contract with itself. Break that contract and the language will tell you exactly how and where. These languages can provide immediate feedback to the developer about a program’s correctness before it is even run. Bridging the gap between all of the existing dynamic code (and the developers who wrote it) and the statically typed utopia lies so-called gradual type systems such as TypeScript.
I like to use a meteorological analogy here: if JavaScript is “wintry mix,” then TypeScript lowers the temperature just enough to build a snowman.
I've been doing Typescript now for about 9 months, so I wouldn't call myself an expert in that, but I also have decades of experience in many other dynamic and static languages (including Haskell), so I can say with confidence that a lot of features in Typescript are to solve problems unique to the Javascript world, and then there are the features in Typescript that are there to solve problems in the other Typescript solutions, and all-in-all while it has great utility and has many fantastic features it just isn't possible to completely overcome the fact that it's a static type system, on top of a dynamic type system.
I've seen a number of posts to the effect of "I'm coming from Typescript and learning Go, how do I do X" and so often the answer has been "Even in Go, even with its very simple type system by static language standards, the answer is that you don't do X at all because X is a feature on top of a feature on top of a feature designed to deal with being on a dynamic language and when you're on a static language you not only don't deploy that solution, you wouldn't even have the problem that's the solution to except that you just created it for yourself by copying Typescript too closely in some other way." A simple example being you don't ask how to type a map/dict/etc. in a normal static language based on the contents of the "type" value, you just declare a properly-typed value in the first place and always use that, at most converting at the edge.
Typescript is a great tool but a very unrepresentative view on how static typing works.
In order to catch problems in dynamic type languages you end up needing a bunch of additional tests to, ironically, verify the expected type. And even then, those tests don't and can't tell you how a method is actually used throughout the program.
Consider the following class
class Human
def initialize(name)
@name = name
end
end
Now imagine you want to refactor this class to something like this class Human
def initialize(first, middle, last)
@fullName = "#{first} #{middle} #{last}"
@first = first
@middle = middle
@last = last
end
end
All the sudden you've got a problem on your hands. You have to find everywhere that referenced `name` on a `Human` object (And don't mess that up, it could be `name` on the `Pet` object) and change them over to full name, or figure out if they are doing something tricky like trying to extract the first name from `name`.Types make refactoring this sort of code somewhat trivial. You simply change the fields and let the compiler tell you ever position which relied on that field.
This extends into all sort of circumstances, like letting the user of a method know what types are expected. For example, the above ruby code doesn't really guarantee that `first` is a string. In fact, it could be an int or even an object and the code would likely handle it just fine. Types expose interface contracts in a quick and easy way for programmers to understand what they are dealing with.
That's really the crux of it. Types are a way to talk to the compiler and it will be your primary target. Humans will also benefit somehow, but it's secundary and they won't be the one who'll give you a tough time.
I see it:
- strict types guarantee the compiler is happy but you'll need to bend yourself to make sure it actually matches your intent
- loose types help you properly express your intent, but you're one the hook to make sure the interpretor does what you said.
You could avoid it by leaving “name” and just adding the additional fields. Maybe that’s the “right” thing to do if you have a ton of consumers and spotty test coverage. But if there’s a lot of fragile code out there (parsing “name” in subtlety different ways, etc.) it’ll never be fixed/improved. Things can get gnarly over time because there’s multiple ways to do something, and everything starts to feel like “legacy code”.
I tend to think that for private codebases, it’s important for breaking changes to be easy.
To be honest, the argument in favor of static typing that I find more compelling is the IDE argument. It definitely is a much richer experience browsing code in an IDE with the benefit of hovering over values and knowing their type, ctrl-clicking to go to where something is defined, et cetera. The equivalent of this interactive experience for dynamically-typed languages was supposed to be the REPL. But I feel like true REPL-driven development has mostly fallen by the wayside, and most environments don't have great support for it.
Whether typechecking or unit tests is "better" is really a question of taste.
In a team, it is just so much easier to come across a "typed" function as opposed to an untyped one. You need to read the entire function to know what it is about.
I personally think that simpler Rust is a better example of the benefits static typing (or maybe something like Gleam).
I feel a little bit about it like I feel about RegEx. Small and simple are good, but once you start diving into the entire catalog of regex capabilities for a single expression you've made a mistake.
Totally agree, however that love of complexity will just squeeze into something else had they not had types to have fun with.