Skip to content

Type Inference

Haskell is able to infer the type of all expressions1.

A simple example you can run in the repl:

> exampleValue = True
> :t exampleValue
exampleValue :: Bool

A more complex example:

data ChessPiece = Piece PieceType Color
data Color = Black | White
data PieceType = Bishop | Rook

isPieceWhite = \case
    Piece _ White -> True
    _ -> False

Haskell's inference about the type of the whole program is shown here, via the Haskell Language Server, which offers to autocomplete it for you. (If you don't have the Haskell Language Server, do the same in the repl with :t isPieceWhite)

Haskell Language Server

One can also mouse over any expression in the program:

Haskell Language Server

(If you don't have the Haskell Language Server, do the same by replacing the expression in question with an _)

Type-driven development

The expressive types and automatic inference let you develop a complicated program top-down, by starting with the type signature of the whole program and filling in gaps incrementally. For example:

main :: IO ()
main = runInputT defaultSettings $ flip evalStateT initBoard $ forever $ do
    line <- lift requestLine 
    let instruction = parseInput line
    board <- get
    let result = evaluate instruction board
    case result of
        Right (BoardUpdate update) -> modify update
        Right DisplayBoard -> lift $ outputStr $ T.unpack $ display board
        Left err -> lift $ outputStr err

    where 

        evaluate = undefined
        display = undefined

Here, undefined stands for a piece of code that is yet to be written. The compiler will typecheck this program, even with the undefineds left in, so you can be sure that it is consistent, before proceeding to fill in the gaps.

More importantly, it will infer the type of each undefined, like so:

Inference

Especially for more complex programs, the compiler's understanding of the types of unwritten parts of your program can be invaluable.

Type based refactoring

Because types are expressive in Haskell, and type errors are static, refactoring a codebase can be performed with the help of the compiler:

type Position = (Double, Double)

Here, if we changed the definition of Position to type Position = (Double, Double, Double), we would be shown a list of compiler errors (in the Problems tab in VSCode's terminal), which we could then fix one-by-one until the code compiles again.


  1. There are exceptions, but they involve advanced techniques. 

Comments