When comparing practical type systems in this day and age, I think a very important distinction is whether the types are enforced. In particular, if I have a function that declares its argument to be of type (non-null) integer, do I ever have to check whether I actually got an integer (or could a null/None etc. or a string ever slip through at runtime)? With today’s mainstream scripting languages that got slapped a type checker in front, you never have that guarantee. But in Haskell there’s no meaningful way you could get anything other than an Int
if your argument is declared as Int
. (I tend to keep bottom outside this discussion, but some may treat it as a null and I would definitely keep memory corruption outside this discussion)
I think this certainty of types have a very strong influence on how people use types. For example, in a Python codebase (even one that has fully embraced types), relying on type information alone doesn’t cut it, you must have 100% code coverage to have confidence that you won’t get a runtime error from adding an integer to a string. That’s why I think every Haskell codebase has 100% test coverage even without a test suite 
I would say not, -fdefer-type-errors
is very different from gradual typing. In a scripting language with a gradual type system, you can declare the type of something as Any
and then you can pass it to the print()
function, and if it happens to be something that can be printed at runtime, it gets printed. You can’t do that with -fdefer-type-errors
in Haskell. As soon as the expression with the deferred type error gets evaluated, you get a runtime exception, regardless of whether the runtime values would happen to have worked if you interpreted the Haskell code.
On the flip side, -fdefer-type-errors
doesn’t make Haskell’s type system any less sound, because you never enable it in production, and you can completely ignore it as long as it’s disabled.
Having said all this, I think an important distinction that Haskell has over the type systems of mainstream languages (be it gradually typed scripting languages, or rigidly typed imperative languages like Java etc.) is that in Haskell you actually bother to “prove” things with types.
What I mean is that in mainstream languages, types are there to make sure that an argument is an int
and not a string
. The more advanced language features like generics etc. all work towards propagating this information from one place to another, but at the end of the day, it’s all about “is this an int or a string?”. In Haskell, you obviously get the “int or string” bit sorted out as well, but thanks to functional purity, parametricity and the advanced type system features like HKTs, GADTs etc. along with the culture of semantics and lawful programming, Haskell also allows you to express non trivial theorems about the code in its types and people do make use of this in real world production code.