Ruby, Python, Erlang/Elixir, Lisp/Clojure, etc, all doing just fine without it. (Some, with some lightweight annotative-oriented typing for a subset of projects.)
The best counterargument to static typing I can articulate is that in general, the only way to be sure software works is to run it. And so, ultimately you need to backstop your software with testing, QA, and observability infrastructure to actually execute it and validate it. At that point, the question is what marginal gains remain from catching a subset of those problems at complile time, also incorporating a potential a false sense of security which may lead to less actual execution validation, and more user-facing issues. The other potential tradeoff of static typing is if the software design and architecture changes for better or worse, or if the velocity of shipping changes changes. I believe there isn't yet any hard evidence of which way this tilts in either case.
There's also a lot of innovation in the area of validation-by-execution as well, and so I think that will result in continual debate: state-of-the-art static typing may benefit compared to yesterday's testing/fuzzing tools, but may not compared to state-of-the-art testing/fuzzing tools, for example.
We're doing fine without, but it's definitely a sore point and I'm one of those developers who has become more and more fond of static typing.
- Small codebase. Fine, use both.
- Medium codebase. Fine, use both.
- Large codebase. You're definitely going to have some typing bugs with dynamically typed languages.
- Humongous codebase. You're definitely going to have many typing bugs with dynamically typed languages.
This should enable you to catch most errors immediately during development.
What you need is something that can help you test more paths, and static type checking checks all paths. Further, they can check all paths without writing any tests (you should still have tests, but you need fewer with a static type system to keep your bug rate the same). Moreover, static type checkers catch errors almost immediately, so you get feedback about your errors sooner rather and more frequently, so you spend less time investing in code that will have to be rewritten. Lastly, they put rails on the code—it’s harder to write bad code including gratuitously magical or abstract code, so your coworkers aren’t writing as much bas code that you have to interface with. If you find static type systems to be frustrating, you’re probably the problematic coworker. :)
This is not a Python problem, per se. It is a developer problem. It is a human problem.
However, with your own code and libraries, you can enforce your own rigor. Keep your functions smaller, test all the inputs, and validate all the outputs. Although you may end up with smaller production code, but more unit tests.
The unit tests are good, as they give you evidence that you did cover those corner cases.
However, the good thing, is that if the functions are smaller, and well unit tested, then it becomes rare that you have to modify it at a later date. And especially if you think through all the different angles, and try to future proof it. Now you have the ability to do functional composition. Just keep chaining the outputs of one function into another.
the question is if those programs were written in statically typed languages, how the world would differ, in-full. for example, they may have been built faster or sloer. or they may have better or worse test coverage, affecting severe defect count overall. or you may have had to hire a different team, with tradeoffs. hard to say.