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 when coming from C. One of the warnings I frequently get is "Top level binding with no type signature" and then the compiler tells me what the type signature should be.
I am missing what the advantage of having the type signature explicitly defined is. As a concrete example:
-- matchStr :: String -> String -> Maybe (String)
matchStr str s
| isPrefixOf str s = Just(drop (length str) s)
| otherwise = Nothing
Now what happens if I want to change the type from String to ByteString to improve performance; I'll have to import the ByteString package and use a qualified version of some functions. No other changes are necessary. If I have the type signature then I also have to change this and yet the Haskell compiler would notice this change and correctly infer the new types.
So what am I missing? Why is it considered a good idea to explicitly put type signatures on functions in the general case? i.e. I understand that there might be exceptions where it is a good idea, but why is it considered good in general?
Function signatures provide a much more powerful and specific way to think about function usage and types. We can write signatures for functions that take a fixed number of arguments: circle : number, string, string -> image.
In C and C++, the type signature is declared by what is commonly known as a function prototype. In C/C++, a function declaration reflects its use; for example, a function pointer with the signature (int)(char, double) would be called as: char c; double d; int retVal = (*fPtr)(c, d);
From HaskellWiki. A type signature is a line like. inc :: Num a => a -> a. that tells, what is the type of a variable. In the example inc is the variable, Num a => is the context and a -> a is its type, namely a function type with the kind * -> * .
In Haskell, every statement is considered as a mathematical expression and the category of this expression is called as a Type. You can say that "Type" is the data type of the expression used at compile time. To learn more about the Type, we will use the ":t" command.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With