Where this falls apart, the more verbose writing style hasn't been proven to convey more information or in a better way. That's an assumption still tossed around.
And typed or untyped, you’re only ever reasoning about the types in the context you’re working in, not the entire program.
A Python function can be called from within or from outside of your codebase with different callsites passing different types.
This can make it much harder to actually reason about the code, while making it seem easier to reason about. Most people would agree w/ your reasoning on a short piece of logic, which then at runtime spectacularly fails because the inputs don't adhere to the types you expected. In a statically typed language you would not even have gotten it to compile and while it might not feel like a bug is being prevented and actually feel tedious, every time your IDE (or compiler) tells you that the type on something is wrong, you've prevented a potential bug.
Let's say we compare Javascript and Typescript (as they're so close but one has static typing.
const myFunc = (param) => {
doSomethingWith(param?.property);
}
Easy, right? Well, does param actually have `property`? No idea. What type is `property`? Does the function `doSomethingWith` take that kind of input? No idea. Now I have to check that function, which might be coming from I don't know where, I might not even have an IDE that can reliably determine where `doSomethingWith` is coming from exactly. Even if I can navigate there now I have to check that piece of code and any other code it calls with `property`. Maybe `property` itself is an object and `doSomethingWith` assumes it has yet another property. This can easily go quite deep and I will not be able to easily reason about this at all. You can't tell me that someone can have all possible runtime combinations of this in his head for any reasonably sized program.Now let's take something that is almost equal but slightly longer to read and write, same thing in Typescript. I've had to define the types of these things somewhere once. Big deal.
const myFunc = (param: SomeType) => {
doSomethingWith(param.property);
}
Notice how this is really not much of a difference. Just a type declaration and it gives me a lot of safety. Let's assume SomeType defined `property` as non-null, so no `?` needed, I know my inputs have already been checked. `doSomethingWith` also defines its parameter type correctly and we know what `property` is or isn't. No need to know anything from the top of my head or spend time digging through code myself. The compiler knows that I am passing the correct type of object along and I won't get a runtime error (well, OK, it's Typescript, so let's also assume I'm not in a mixed TS/JS code base where I might very easily get `any` kind of object.Now syntax will be a little bit different, but I would argue the exact same thing in say Java or Kotlin is equivalently short and readable (yes even in Java!) while benefiting from even more type safety:
public myFunc(SomeType param) {
doSomethingWith(param.getProperty());
}
Didn't really hurt much, did it?But these are super simple example. It get can arbitrarily complex.
But Java, Kotlin, and Typescript types are very weak sauce. When types can do more, we can do more with them.
I associate verbosity with object-oriented programming, whether statically typed or not.
Here's something I can express with static typing that I can't express with dynamic typing: "this function returns a function which returns an integer for every input". There's no test you could write to verify this property. So I'm inclined to say that static typing is more expressive, since it gives me a way to express and verify properties like this.
Without compile-time types, you are not equipped to express serious compile-time work.