Type inference
Encyclopedia : T : TY : TYP : Type inference
Type inference is a feature present in some strongly statically typed programming languages. It is often characteristic of — but not limited to — functional programming languages in general. Some languages that include type inference are: Haskell, Cayenne, Clean, ML, OCaml, Epigram, Scala, Nemerle, D and Boo. This feature is planned for Fortress, C# 3.0 and [C++0x].
Type inference refers to the ability to automatically either partially or fully deduce the type of the value derived from the eventual evaluation of an expression. As this process is systematically performed at compile time, the compiler is often able to infer the type of a variable or the type signature of a function, without explicit type annotations having been given. In many cases, it is possible to omit type annotations from a program completely if the type inference system is robust enough, or the program or language simple enough.
To obtain the information required to correctly infer the type of an expression lacking an explicit type annotation, the compiler either gathers this information as an aggregate and subsequent reduction of the type annotations given for its subexpressions (which may themselves be variables or functions), or through an implicit understanding of the type of various atomic values (e.g., () : Unit; true : Bool; 42 : Integer; 3.14159 : Real; etc.). It is through recognition of the eventual reduction of expressions to implicitly typed atomic values that the compiler for a type inferring language is able to compile a program completely without type annotations. In the case of highly complex forms of higher order programming and polymorphism, it is not always possible for the compiler to infer as much, however, and type annotations are occasionally necessary for disambiguation.
Example
For example, let us consider the Haskell functionlength, which may be defined as:
length [] = 0 length (first:rest) = 1 + length restFrom this, it is evident that the function handles lists as inputs, and the base case of this recursive function returns an integer (Haskell "Int"). So we can reliably construct a type signature
length :: [a] -> IntSince there are no ad-hoc polymorphic subfunctions in the function definition, we can declare the function to be parametric polymorphic.
Hindley-Milner type inference algorithm
The common algorithm used to perform the type inference is the one now commonly referred to as Hindley-Milner or Damas-Milner algorithm.The origin of this algorithm is the type inference algorithm for the simply typed lambda calculus, which was devised by Haskell B. Curry and Robert Feys in 1958.
In 1969 Roger Hindley extended this work and proved that their algorithm always inferred the most general type.
In 1978 Robin Milner, independently of Hindley's work, provided an equivalent algorithm,
In 1985 Luis Damas finally proved that Milner's algorithm is complete and extended it to support systems with polymorphic references.
The Algorithm
The algorithm proceeds in two steps. First, we need to generate a number of equations to solve, then we need to solve them.Generating the equations
The algorithm used for generating the equations is similar to a regular type checker, so let's consider first a regular type checker for the typed lambda calculus given by[e \, ::= E \mid v \mid (\lambda v:\tau. e) \mid (e\, e)]
and
[\tau \, ::= T \mid \tau \to \tau ]
where [E] is a primitive expression (such as "3") and [T] is a primitive type (such as "Integer").
We want to construct a function [f] of type [\epsilon \to t \to \tau], where [\epsilon] is a type environment and [t] is a term. We assume that this function is already defined on primitives. The other cases are:
[f\, \Gamma\, v = \tau] whenever the binding [v\, :\,\tau] is in [\Gamma]
[f\, \Gamma\, (g\, e) = \tau] whenever [\tau_1 = \tau_2 \to \tau] where [\tau_1 = f\, \Gamma\, g] and [\tau_2 = f\, \Gamma\, e].
[f\, \Gamma\, (\lambda v:\tau. e) = \tau \to \tau_e] where [\tau_e = f\, \Gamma'\, e] and [\Gamma'] is [\Gamma] extended by the binding [ v \,:\,\tau].
Note that so far we do not specify what to do when we fail to meet the various conditions. This is because, in the simple type *checking* algorithm, the check simply fails whenever anything goes wrong.
Now, we develop a more sophisticated algorithm that can deal with type variables and constraints on them. Therefore, we extend the set T of primitive types to include an infinite supply of variables, denoted by lowercase Greek letters [\alpha, \beta, ...]
This is a limited overview. For now, refer to Types and Programming Languages by Benjamin Pierce, Sections 22.1-4 .
Solving the equations
Solving the equations proceeds by unification. This is - maybe surprisingly - a rather simple algorithm. The function [u] operates on type equations and returns a structure called a "substitution". A substitution is simply a mapping from type variables to types. Substitutions can be composed and extended in the obvious ways.Unifying the empty set of equations is easy enough: [u\, \emptyset = \mathbf], where [\mathbf] is the identity substitution.
Unifying a variable with a type goes this way: [u\, ([alpha = T] \cup C) = u\, (C') \cdot (\alpha \mapsto T)], where [\cdot] is the substitution composition operator, and [C'] is the set of remaining constraints [C] with the new substitution [\alpha \mapsto T] applied to it.
Of course, [u\, ([T = alpha] \cup C) = u ([alpha = T] \cup C)].
The interesting case remains as [u\, ([S to S' = T to T']\cup C) = u \, (\\cup C)].
References
External link
- [Archived e-mail message] by Roger Hindley explaining the history of type inference
From Wikipedia, the Free Encyclopedia. Original article here. Support Wikipedia by contributing or donating.
All text is available under the terms of the GNU Free Documentation License See Wikipedia Copyrights for details.
