In my experience the most problematic bugs are the bugs in peoples thinking about the problem. Those bugs are easy to translate into "working code", because they carry through all the way from the top.
Then I much rather want a program that is easily malleable, than one where the flawed logic is encoded in the types.
If the problem space is known in detail upfront, then sure. But then writing test cases to cover them is just as simple.
Not least because if you really want to, writing test cases and type checks to enforce typing is trivial to do in dynamic languages too. In fact there are quite a few examples on how to do advanced type checks on Ruby code. With the level of introspection Ruby does, you can e.g. easily "wrap" every method with detailed enforcement of type invariants.
You need to run some functional tests to exercise the code paths to trigger said type checks, but you need functional tests anyway.
People don't tend to use those methods because in practice they turn out to rarely capture many interesting errors once you write functional tests anyway.
>Those bugs are easy to translate into "working code", because they carry through all the way from the top.
Actually, they aren't. Try haskell or ocaml or clean or SML sometime. You have to have a clear and consistent model of what you are doing in order for your code to type check. I can't count the number of times I've had my incorrect thinking about a problem be forced out of me during the initial development by the compiler.
>Not least because if you really want to, writing test cases and type checks to enforce typing is trivial to do in dynamic languages too
No, it is actually a huge pain, you need to write a type checker. Writing a "is this an int?" test does not give you the assurance of type checking. That only finds out if the value happened to be an int under that particular execution of the application. A type checker completely guarantees that there is no way for it to be the wrong type ever. Even if the only thing static typing got you was the complete elimination of NULL/nil exceptions, it would be worth it. But it gets you way more than that.
Then I much rather want a program that is easily malleable, than one where the flawed logic is encoded in the types.
If the problem space is known in detail upfront, then sure. But then writing test cases to cover them is just as simple.
Not least because if you really want to, writing test cases and type checks to enforce typing is trivial to do in dynamic languages too. In fact there are quite a few examples on how to do advanced type checks on Ruby code. With the level of introspection Ruby does, you can e.g. easily "wrap" every method with detailed enforcement of type invariants.
You need to run some functional tests to exercise the code paths to trigger said type checks, but you need functional tests anyway.
People don't tend to use those methods because in practice they turn out to rarely capture many interesting errors once you write functional tests anyway.