It's still an advantage, even if you write type signatures, because the compiler will catch type errors in your functions. I usually write type signatures too, but omit them in places like where
or let
clauses where you actually define new symbols but don't feel the need to specify a type signature.
Stupid example with a strange way to calculate squares of numbers:
squares :: [Int]
squares = sums 0 odds
where
odds = filter odd [1..]
sums s (a:as) = s : sums (s+a) as
square :: Int -> Int
square n = squares !! n
odds
and sums
are functions that would need a type signature if the compiler wouldn't infer them automatically.
Also if you use generic functions, like you usually do, type inference is what ensures that you really combine all those generic functions together in a valid way. If you, in the above example, say
squares :: [a]
squares = ...
The compiler can deduce that this isn't valid this way, because one of the used functions (the odd
function from the standard library), needs a
to be in the type class Integral
. In other languages you usually only recognize this at a later point.
If you write this as a template in C++, you get a compiler error when you use the function on a non-Integral type, but not when you define the template. This can be quite confusing, because it's not immediately clear where you've gone wrong and you might have to look through a long chain of error messages to find the real source of the problem. And in something like python you get the error at runtime at some unexpected point, because something didn't have the expected member functions. And in even more loosely typed languages you might not get any error, but just unexpected results.
In Haskell the compiler can ensure that the function can be called with all the types specified in it's signature, even if it's a generic function that is valid for all types that fulfill some constrains (aka type classes). This makes it easy to program in a generic way and use generic libraries, something much harder to get right in other languages. Even if you specify a generic type signature, there is still a lot of type inference going on in the compiler to find out what specific type is used in each call and if this type fulfills all the requirements of the function.