Algorithm W
Algorithm W
Martin Grabmüller
Sep 26 2006∗
Abstract
In this paper we develop a complete implementation of the classic algorithm W for Hindley-
Milner polymorphic type inference in Haskell.
1 Introduction
Type inference is a tricky business, and it is even harder to learn the basics, because most pub-
lications are about very advanced topics like rank-N polymorphism, predicative/impredicative
type systems, universal and existential types and so on. Since I learn best by actually developing
the solution to a problem, I decided to write a basic tutorial on type inference, implementing
one of the most basic type inference algorithms which has nevertheless practical uses as the basis
of the type checkers of languages like ML or Haskell.
The type inference algorithm studied here is the classic Algoritm W proposed by Milner [4].
For a very readable presentation of this algorithm and possible variations and extensions read
also [2]. Several aspects of this tutorial are also inspired by [3].
This tutorial is the typeset output of a literate Haskell script and can be directly loaded into
an Haskell interpreter in order to play with it. This document in electronic form as well as the
literate Haskell script are available from Github1 .
This module was tested with version 6.6 of the Glasgow Haskell Compiler [1]
2 Algorithm W
The module we’re implementing is called AlgorithmW (for obvious reasons). The exported
items are both the data types (and constructors) of the term and type language as well as
the function ti , which performs the actual type inference on an expression. The types for the
exported functions are given as comments, for reference.
We start with the necessary imports. For representing environments (also called contexts in
the literature) and substitutions, we import module Data.Map. Sets of type variables etc. will
be represented as sets from module Data.Set.
1
Since we will also make use of various monad transformers, several modules from the monad
template library are imported as well.
The module Text.PrettyPrint provides data types and functions for nicely formatted and
indented output.
2.1 Preliminaries
We start by defining the abstract syntax for both expressions (of type Exp), types (Type) and
type schemes (Scheme).
In order to provide readable output and error messages, we define several pretty-printing func-
tions for the abstract syntax. These are shown in Appendix A.
We will need to determine the free type variables of a type. Function ftv implements this
operation, which we implement in the type class Types because it will also be needed for type
environments (to be defined below). Another useful operation on types, type schemes and the
like is that of applying a substitution.
2
Just t → t
apply s (TFun t1 t2 ) = TFun (apply s t1 ) (apply s t2 )
apply s t =t
Now we define substitutions, which are finite mappings from type variables to types.
Type environments, called Γ in the text, are mappings from term variables to their respective
type schemes.
We define several functions on type environments. The operation Γ\x removes the binding for
x from Γ and is called remove.
The function generalize abstracts a type over all type variables which are free in the type but
not free in the given type environment.
Several operations, for example type scheme instantiation, require fresh names for newly
introduced type variables. This is implemented by using an appropriate monad which takes
care of generating fresh names. It is also capable of passing a dynamically scoped environment,
error handling and performing I/O, but we will not go into details here.
3
runTI t = runState (runExceptT t) initTIState
where initTIState = 0
newTyVar :: TI Type
newTyVar =
do s ← get
put (s + 1)
return (TVar (reverse (toTyVar s)))
where
toTyVar :: Int → String
toTyVar c | c < 26 = [toEnum (97 + c)]
| otherwise = let (n, r ) = c ‘divMod ‘ 26
in (toEnum (97 + r )) : toTyVar (n − 1)
The instantiation function replaces all bound type variables in a type scheme with fresh type
variables.
This is the unification function for types. The function varBind attempts to bind a type variable
to a type and return that binding as a subsitution, but avoids binding a variable to itself and
performs the occurs check.
The function ti infers the types for expressions. The type environment must contain bindings
for all free variables of the expressions. The returned substitution records the type constraints
imposed on type variables by the expression, and the returned type is the type of the expression.
4
ti :: TypeEnv → Exp → TI (Subst, Type)
ti (TypeEnv env ) (EVar n) =
case Map.lookup n env of
Nothing → throwError $ "unbound variable: " ++ n
Just sigma → do t ← instantiate sigma
return (nullSubst, t)
ti env (ELit l ) = tiLit env l
ti env (EAbs n e) =
do tv ← newTyVar
let TypeEnv env 0 = remove env n
env 00 = TypeEnv (env 0 ‘Map.union‘ (Map.singleton n (Scheme [ ] tv )))
(s1 , t1 ) ← ti env 00 e
return (s1 , TFun (apply s1 tv ) t1 )
ti env (EApp e1 e2 ) =
do tv ← newTyVar
(s1 , t1 ) ← ti env e1
(s2 , t2 ) ← ti (apply s1 env ) e2
s3 ← mgu (apply s2 t1 ) (TFun t2 tv )
return (s3 ‘composeSubst‘ s2 ‘composeSubst‘ s1 , apply s3 tv )
ti env (ELet x e1 e2 ) =
do (s1 , t1 ) ← ti env e1
let TypeEnv env 0 = remove env x
t 0 = generalize (apply s1 env ) t1
env 00 = TypeEnv (Map.insert x t 0 env 0 )
(s2 , t2 ) ← ti (apply s1 env 00 ) e2
return (s1 ‘composeSubst‘ s2 , t2 )
This is the main entry point to the type inferencer. It simply calls ti and applies the returned
substitution to the returned type.
2.3 Tests
The following simple expressions (partly taken from [2]) are provided for testing the type infer-
ence function.
5
(ELet "x" (EApp (EVar "y") (ELit (LBool True)))
(EVar "x")))
This simple test function tries to infer the type for the given expression. If successful, it prints
the expression together with its type, otherwise, it prints the error message.
test :: Exp → IO ()
test e =
let (res, ) = runTI (typeInference Map.empty e)
in case res of
Left err → putStrLn $ show e ++ "\nerror: " ++ err
Right t → putStrLn $ show e ++ " :: " ++ show t
main :: IO ()
main = mapM test [e0 , e1 , e2 , e3 , e4 , e5 ]
3 Conclusion
This literate Haskell script is a self-contained implementation of Algorithm W [4]. Feel free to
use this code and to extend it to support better error messages, type classes, type annotations
etc. Eventually you may end up with a Haskell type checker. . .
References
[1] GHC Developers. Glasgow Haskell Compiler Homepage. Available from: http://www.
haskell.org/ghc, 2008. Last visited: 2008-10-07.
[2] Bastiaan Heeren, Jurriaan Hage, and Doaitse Swierstra. Generalizing Hindley-Milner type
inference algorithms. Technical Report UU-CS-2002-031, Institute of Information and Com-
puting Sciences, Utrecht University, 2002.
[3] Mark P. Jones. Typing Haskell in Haskell. In Proceedings of the 1999 Haskell Workshop,
1999. Published in Technical Report UU-CS-1999-28, Department of Computer Science,
University of Utrecht.
[4] Robin Milner. A theory of type polymorphism in programming. Journal of Computer and
System Sciences, 17:348–375, 1978.
A Pretty-printing
This appendix defines pretty-printing functions and instances for Show for all interesting type
definitions.
6
prType :: Type → PP .Doc
prType (TVar n) = PP .text n
prType TInt = PP .text "Int"
prType TBool = PP .text "Bool"
prType (TFun t s) = prParenType tPP . h+i PP .text "->"PP . h+i prType s
prParenType :: Type → PP .Doc
prParenType t = case t of
TFun → PP .parens (prType t)
→ prType t
instance Show Exp where
showsPrec x = shows (prExp x )
prExp :: Exp → PP .Doc
prExp (EVar name) = PP .text name
prExp (ELit lit) = prLit lit
prExp (ELet x b body) = PP .text "let"PP . h+i
PP .text x PP . h+i PP .text "="PP . h+i
prExp bPP . h+i PP .text "in" PP .$$
PP .nest 2 (prExp body)
prExp (EApp e1 e2 ) = prExp e1 PP . h+i prParenExp e2
prExp (EAbs n e) = PP .char ’\\’PP . h+i PP .text nPP . h+i
PP .text "->"PP . h+i
prExp e
prParenExp :: Exp → PP .Doc
prParenExp t = case t of
ELet → PP .parens (prExp t)
EApp → PP .parens (prExp t)
EAbs → PP .parens (prExp t)
→ prExp t
instance Show Lit where
showsPrec x = shows (prLit x )
prLit :: Lit → PP .Doc
prLit (LInt i ) = PP .integer i
prLit (LBool b) = if b then PP .text "True" else PP .text "False"
instance Show Scheme where
showsPrec x = shows (prScheme x )
prScheme :: Scheme → PP .Doc
prScheme (Scheme vars t) = PP .text "All"PP . h+i
PP .hcat
(PP .punctuate PP .comma (map PP .text vars))
PP . <> PP .text "."PP . h+i prType t
Acknowledgements
Thanks to Franklin Chen, Kotolegokot, Christoph Höger and Richard Laughlin, who have con-
tributed fixes and updates for newer GHC versions over the years.