Skip to content
AgileLogics
6 min read

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 Int and Double — use fromIntegral.
  • Ambiguous types: print (read "5") fails, because GHC can't tell which type to read. Add an annotation: print (read "5" :: Int).