I had a really hilarious bug today where under some circumstances all the text in the display would be replaced by numbers. Small integers, each placed where the word should be.
What had happened is that I'd added a layer of indirection; where previously, after line wrapping, the data structure for a rendered paragraph was an array of pointers to the word objects, now it was an array of indices into the paragraph's word array. And I'd forgotten about one particular exotic code path. Lua was seeing the array of indices, automatically casting the ints to strings, and then using those strings instead of the word data itself...
Static types would have made this bug impossible.
...way back when, there was a really nice and thoroughly obscure language called Strongtalk; it was a Smalltalk 80 clone with optional strict types. You could annotate your classes and methods with type information. If it was there, it would be checked; if it wasn't, you got the traditional behaviour. The JIT knew about the type information and could use it to produce really fast code. It combined the ultimate dynamic language with an expressive static type system (complete with parametric polymorphism).
It was open sourced in 2006 and sank without trace. Sigh.
int[] bodies = {1, 2, 3};
for (int body : bodies) {
String formatted_body = "<p>" + body + "</p>";
callMethodWithStrArg(formatted_body);
}
And if in this hypothetical case it was previously a `String[] bodies` and a `String body`, I bet a lot of programmers would use an auto-refactoring tool because "static types and auto-refactoring go together for being confident in changes like apples and pie" and I bet the error wouldn't have been noticed even at review time. God help you if you're using a static language without generics that has implicit type conversions. In Python, though, this raises an error: bodies = [1, 2, 3]
for body in bodies:
formatted_body = '<p>' + body + '</p>'
The error is: "TypeError: cannot concatenate 'str' and 'int' objects".Dynamically typed languages still have types.
I had totally forgotten that Java does it too, despite having done `""+i` lots of times as a cheap and easy and evil way to convert numbers to strings.
...I am currently rewriting a big chunk of the primary data storage to use immutable data structures, because it makes implementing Undo easier. I am having to fight the urge to redo it all in Haskell.
Ignoring trivial cases and dependent types, types tell you one of two things: "this might be correct" or "this is incorrect". Again ignoring trivial cases where exhaustive checking is possible, that's the same thing that tests tell you - and it's a hugely useful thing to be told!
It is a substitute for those unit tests that are essentially checking type invariants. It is not a substitute for all unit tests, but I don't think anyone made (much less intended to make) that claim anywhere in this thread.
"They only tell you that you are matching function signatures correctly."
All of computation can be expressed as application of functions, so for sufficiently expressive function signatures that's not much of a limitation. Of course, if you want to guarantee that your compiles terminate, you need to apply some limits to the expressiveness of your function signatures... but there are powerful guarantees you can get out of even so simplistic a type system as C's, if you work with it rather than against it.
Types are one mechanism for static analysis. Better contracts (nullability, valid ranges, etc) goes much further.
Compiles, runs, fails if i is out of bounds. Which means that you need to test the code that you write, and that even if you have 100% line coverage (or branch coverage or MC/DC coverage or...), it doesn't mean i won't get the wrong value.
People who claim that "once my C++/Haskell/Agda/whatever program compiles, I know it probably has no bugs" thus tempt others to mention "a false sense of security." (Agda might be going further than anyone else with proving that i cannot be out of bounds, AFAIK... though a generally undecidable problem will remain generally undecidable.)
print(arr[i])
You can write this, but you probably wouldn't. print (arr `V.unsafeIndex` i)I have seen claims and have a hunch that more than just very silly tests can be eliminated by types, but I'm struggling to come up with or remember any examples.
I'm hoping someone who has one will reply with one of these claims or any examples.
Quite often I write some Rust code and know that it will work if it compiles (though I still write tests)
If however you say, "hey, I seriously don't want to cover all the lines including say error messages", then in Python,
if error: print obj.name
...might be a problem because obj doesn't have a name; in C++,
if(error) cout << obj.name;
...might only blow up because obj is a null reference (which is what you get when you dereference the null pointer, in practice, even though null references aren't supposed to exist); and in a language with non-nullable pointers, the equivalent of the above can only blow up if printing the name (which is surely valid if obj is non-nullable and type-checks as having a name) somehow blows up, which for a string in a memory-safe language is very very unlikely.
So if you leave uncovered lines in your code which we all do then yes, the stricter the type system, the better your chances are, statistically, all else being equal (for instance, the number of lines being the same... which might not be the case.)
Overall the silly tests people sometimes write in dynamic languages are needless IMO and result from over-applying "TDD" or "unit testing" or some other buzzword and/or paranoia of someone coming from a statically typed language background. I personally think I have pretty much the same amount of tests regardless of the type system.