Types and Type Inference
Prerequisites: Numbers and arithmetic
Haskell's compiler infers types: it works out the type of every expression from how it's used, so you can leave most signatures out.
ghci> :t True && False
True && False :: Bool
ghci> :t length
length :: Foldable t => t a -> Int
ghci> :t [True, False]
[True, False] :: [Bool]
ghci> :t ('x', "yz")
('x', "yz") :: (Char, String)
Reading function types
-> separates argument types from the result type:
ghci> :t not
not :: Bool -> Bool
ghci> import Data.Char
ghci> :t toUpper
toUpper :: Char -> Char
ghci> :t words
words :: String -> [String]
Read not :: Bool -> Bool as "not takes a Bool and returns a Bool".
The type alone often tells you what a function does.
Type variables: polymorphism
ghci> :t reverse
reverse :: [a] -> [a]
ghci> :t fst
fst :: (a, b) -> a
A lowercase name in a type, like a, is a type variable: reverse
works on a list of any type, and returns a list of the same type. This
is called parametric polymorphism — "generics" in other languages.
Constraints
ghci> :t (+)
(+) :: Num a => a -> a -> a
ghci> :t 42
42 :: Num a => a
ghci> :t (==)
(==) :: Eq a => a -> a -> Bool
Num a => is a constraint: a can be any type, as long as it's a
numeric type. Eq a means "any type whose values can be compared for
equality". These are type classes, covered in Level 2. Notice that
even the literal 42 is polymorphic — it can be an Int, Integer, or
Double depending on where you use it.
Should you write signatures?
Yes, for every top-level definition. They document your intent, make
error messages point at the right place, and GHC warns about missing
ones with -Wall. Inside functions, let inference do the work.
greet :: String -> String
greet name = "Hello, " ++ name ++ "!"
main :: IO ()
main = do
putStrLn (greet "Grace")
let n = length (greet "Grace")
print n
Hello, Grace!
13
Here n needs no annotation: GHC knows length returns an Int.
Common mistakes
- Writing types in lowercase (
int) — types are capitalized. - Expecting automatic conversion between
IntandDouble— usefromIntegral. - Ambiguous types:
print (read "5")fails, because GHC can't tell which type to read. Add an annotation:print (read "5" :: Int).