I am trying to code using \'good Haskell style\' and so am trying to follow typical coding standards I find around. Also, compiling with -Wall and -Werror as I am used to w
There are several reasons.
FilePath
vs String
.)It is worth noting that some people advocate that you start with the type signatures, and fill in the implementations later.
At any rate, most people seem to recommend type signatures for all or most top-level declarations. Whether you give them for local variables / functions is a matter of taste.
Your contrived example is really contrived, since the function body does not depend on the type of contents of the list. In this case it is indeed difficult to see what's the benefit of defining the type to be [String] -> ([String],[String])
instead of [a]->([a],[a])
If you attempt to define a function that depends on the contents, you will see that the type definition is not the only thing you need to change. For example, changing a list for MArray
is going to be far more involved, not just using a function that happens to have the same name in a different module. So qualifying the name during refactoring in a few narrow cases is not a good enough reason to not specify type signatures.
Specifying the type tells the compiler a little bit of the intent. Then the compiler will be able to report the mismatch of the intent and the implementation.
If you make a mistake in defining your function, the compiler might infer a type that isn't what you expected it to be. If you've declared the type you expect, the compiler will report the error in the function's definition.
Without the declaration, the compiler has no way to know that its inferred type is "wrong", and it will instead end up reporting errors in the places where you try to call the function, which makes it less clear where the problem really lies.
If the calling functions don't have type declarations either, then instead of reporting errors there, the compiler might just infer incorrect types for those too, causing problems in their callers. You'll end up getting an error message somewhere, but it may be quite far removed from the actual root of the problem.
Also, you can declare a more specific type than what the compiler would infer. For example, if you write the function:
foo n = n + 1
The compiler will infer the type Num a => a -> a
, which means it must compile generic code that can work with any Num
instance. If you declare the type as Int -> Int
, the compiler may be able to produce more efficient code that's specialized for integers only.
Finally, type declarations serve as documentation. The compiler may be able to infer the types of complex expressions, but it's not so easy for a human reader. A type declaration provides the "big picture" that can help a programmer understand what the function does.
Note that Haddock comments are attached to declarations, not definitions. Writing a type declaration is the first step toward providing additional documentation for a function using Haddock.
I would consider documentation one advantage of having an explicit type signature.
From "Types and Programming Languages":
Types are also useful when reading programs. The type declarations in procedure headers and module interfaces constitute a form of documentation, giving useful hints about behavior. Moreover, unlike descriptions embedded in comments, this form of documentation cannot become outdated, since it is checked during every run of the compiler. This role of types is particularly important in module signatures.