Signatures aren't just for documentation (even though they are very useful for that as well). They are enforced by the compiler, which means that by adding signatures you can make the types of your functions more restrictive than they would be otherwise. Toy example:
add x y = x + y
addInt :: Int -> Int -> Int
addInt x y = x + y
*Main> :t add
add :: Num a => a -> a -> a
*Main> add 2 3
5
*Main> add 2.1 3.1
5.2
*Main> :t addInt
addInt :: Int -> Int -> Int
*Main> addInt 2 3
5
*Main> addInt 2.1 3.1 -- addInt will not accept non-Ints.
<interactive>:23:8:
No instance for (Fractional Int) arising from the literal ‘2.1’
In the first argument of ‘addInt’, namely ‘2.1’
In the expression: addInt 2.1 3.1
In an equation for ‘it’: it = addInt 2.1 3.1
Besides that, adding type signatures means you will get better (i.e. easier to understand) errors in tricky situations, as the compiler will know what you want to achieve rather than having to guess everything on its own.
There are also situations in which the compiler can't decide the types without the help of some signatures or other type annotations. Perhaps the simplest example is:
readAndShow s = show (read s)
If you try to use that without specifying any types...
Foo.hs:6:17:
No instance for (Show a0) arising from a use of ‘show’
The type variable ‘a0’ is ambiguous
Note: there are several potential instances:
instance (GHC.Arr.Ix a, Show a, Show b) => Show (GHC.Arr.Array a b)
-- Defined in ‘GHC.Arr’
instance Show a => Show (Maybe a) -- Defined in ‘GHC.Show’
instance (Integral a, Show a) => Show (GHC.Real.Ratio a)
-- Defined in ‘GHC.Real’
...plus 26 others
In the expression: show (read s)
In an equation for ‘readAndShow’: readAndShow s = show (read s)
Foo.hs:6:23:
No instance for (Read a0) arising from a use of ‘read’
The type variable ‘a0’ is ambiguous
Note: there are several potential instances:
instance (GHC.Arr.Ix a, Read a, Read b) => Read (GHC.Arr.Array a b)
-- Defined in ‘GHC.Read’
instance Read a => Read (Maybe a) -- Defined in ‘GHC.Read’
instance (Integral a, Read a) => Read (GHC.Real.Ratio a)
-- Defined in ‘GHC.Read’
...plus 25 others
In the first argument of ‘show’, namely ‘(read s)’
In the expression: show (read s)
In an equation for ‘readAndShow’: readAndShow s = show (read s)
Failed, modules loaded: none.
... it won't work. read
converts a String
to some type, and show
does the opposite. However, if nothing specifies the type of read s
, the compiler can't tell which type you want to read the String
as. So you either need to specify the intermediate type...
readAndShowAsInt s = show (read s :: Int)
*Main> readAndShowAsInt "2"
"2"
... Or have something else pick the type for you:
readAndAdd :: String -> Int -> Int
readAndAdd s y = read s + y
*Main> readAndAdd "2" 3
5
data NestedList a = Epsilon | Nested a (NestedList [a])
that represents nested lists. Thelength
function is easily definable:length Epsilon = 0; length (Nested _ xs) = 1 + length xs
. However GHC is unable to infer the correct type (it will yield an error). The problem is that the function contains polymorphic recursion, i.e. we are usinglength
over the typeNestedList [a]
while defining it for the typeNestedList a
and soa
and[a]
don't match. Providing the type signature makes the code compilable. – Bakuriu