Documentation of interfaces in dynamic languages usually always specify the types of arguments and return values, and the code very often looks like this:
def foo(arg)
raise TypeError, "Integer argument expected" unless arg.kind_of?(Integer)
# ...
end
Yup. That saved me a lot of typing (no pun intended) compared to void foo(int arg) {
// ...
}
Reading (and writing) code in dynamic languages (including the tests!) always seems to me to disprove all of their purported benefits and underlines the need for a strong type system.And what is the value of "compile time", if the all this type checking makes the compiler so slow that just compiling is slower than compiling + running unit tests in a simpler language? What if it catches 10% of errors (optimistic estimate) but due to constraints in reusability makes my code 20% bigger (conservative estimate)? Likely around 10% more bugs.
Don't get me wrong: I like static typing (to some degree). I just get antsy when it gets oversold.
I also don't make these mistakes while writing Smalltalk.
You should do those too.
> Which typically also coincidentally check the types (if I check that a value is > 2, I am also checking that its type is number
~$ python -c 'print "foo" > 2'
True
Now, on the bright side, Python 3 fixes that one: ~$ python3 -c 'print(2 < "foo")'
Traceback (most recent call last):
File "<string>", line 1, in <module>
TypeError: unorderable types: int() < str()
But most popular dynamic languages seem to have more where that came from. And if you have a sufficiently robust type system not to, then it starts to look a lot like the type system of a static language, minus the compile-time error detection.> what is the value of "compile time", if the all this type checking makes the compiler so slow that just compiling is slower than compiling + running unit tests in a simpler language?
To the best of my knowledge, type checking is hardly the slowest part of most compilers. Many dynamic languages have somewhat faster startup times because they don't have a compiler, or they use a JIT at runtime. In any case, for many non-trivial programs, runtime dominates. A sufficiently thorough unit test suite may take significantly longer than compilation. And even if not, someone has to write the unit tests that handle what static type checking does for free.
I'm not going to claim that static typing is universally better; for instance, it's a lot harder to provide a good REPL in a statically typed language. But personally, I like dealing with as many errors as possible up front, rather than discovering them later on.
I see this argument from fans of dynamic typing quite a bit. Gary Bernhardt manages to defeat it better in 5 minutes than I could in a lifetime, so here you go:
'asdas' > 2 <Print It> -> Message not understood "SmallInteger>>isByteString"
On further reflection, I'm not even sure how your argument about integers is supposed to work. Implicitly testing that an input is an integer by comparing it to another one in a single unit test case wouldn't guarantee that no other code could pass a non-integer input to the same function from somewhere else in the general case, which is what a static type system would do for you.
The context where your argument does seem to have some merit is in unit testing that the outputs from a function are of the expected type. But even then, there have been plenty of related gotchas in JavaScript, Ruby, Python, PHP, Perl... I'm sure you can pick an example where your particular case (the integer test) gets picked up, and I'm sure some popular dynamic languages are getting better in this area (Python 3 has significant advances over Python 2, for example).
Basically, this feels like you're choosing an invariant and a dynamic language that conveniently support your position, while ignoring numerous other useful invariants and languages that would not. If you get to do that, it seems only fair to judge the potential of strong, static type systems using languages like Haskell or Rust, and I can't think of many unit tests I've ever written in dynamic languages that would implicitly verify that the unit under test didn't have unexpected side effects or did not allow access to resources in ways that might not be thread-safe, even in the specific case being tested rather than the general one.
Surely, you're only testing that it can be coerced to a number?
Couldn't you invert the question for Java and instead ask, why am I wasting my time chasing 100% test coverage when the type constraints of the language guarantee a certain degree of correctness?
I've always found that dynamic language projects require twice as many tests as a Java project to get a similarly reliable test suite.