I see some interesting discussions here about static vs. dynamic typing. I generally prefer static typing, due to compile type checking, better documented code, etc. However, I do agree that they do clutter up the code if done the way Java does it, for example.
So I'm about to start building a functional style language of my own, and type inference is one of the things that I want to implement. I do understand that it is a big subject, and I'm not trying to create something that has not been done before, just basic inferencing...
Any pointers on what to read up that will help me with this? Preferably something more pragmatic/practical as opposed to more theoretical category theory/type theory texts. If there's an implementation discussion text out there, with data structures/algorithms, that would just be lovely.
Type inference is the ability to automatically deduce, either partially or fully, the type of an expression 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.
Standard ML is a strongly and statically typed programming language. However, unlike many other strongly typed languages, the types of literals, values, expressions and functions in a program will be calculated by the Standard ML system when the program is compiled. This calculation of types is called type inference.
Python doesn't do static type inference, because it wants to let you do things that are impossible under such a scheme.
Type inference is a Java compiler's ability to look at each method invocation and corresponding declaration to determine the type argument (or arguments) that make the invocation applicable.
I found the following resources helpful for understanding type inference, in order of increasing difficulty:
However, since the best way to learn is to do, I strongly suggest implementing type inference for a toy functional language by working through a homework assignment of a programming languages course.
I recommend these two accessible homeworks in ML, which you can both complete in less than a day:
These assignments are from a more advanced course:
Implementing MiniML
Polymorphic, Existential, Recursive Types (PDF)
Bi-Directional Typechecking (PDF)
Subtyping and Objects (PDF)
It's unfortunate that much of the literature on the subject is very dense. I too was in your shoes. I got my first introduction to the subject from Programming Languages: Applications and Interpretation
http://www.plai.org/
I'll try to summarize the abstract idea followed by details that I did not find immediately obvious. First, type inference can be thought of generating and then solving constraints. To generate constraints, you recurse through the syntax tree and generate one or more constraints on each node. For example, if the node is a +
operator, the operands and the results must all be numbers. A node that applies a function has the same type as the result of the function, and so on.
For a language without let
, you can blindly solve the above constraints by substitution. For example:
(if (= 1 2) 1 2)
here, we can say that the condition of the if statement must be Boolean, and that the type of the if statement is the same as the type of its then
and else
clauses. Since we know 1
and 2
are numbers, by substitution, we know the if
statement is a number.
Where things get nasty, and what I couldn't understand for a while, is dealing with let:
(let ((id (lambda (x) x))) (id id))
Here, we've bound id
to a function that returns whatever you've passed in, otherwise known as the identity function. The problem is the type of the function's parameter x
is different on each usage of id
. The second id
is a function of type a -> a
, where a
can be anything. The first is of type (a -> a) -> (a -> a)
. This is known as let-polymorphism. The key is to solve constraints in a particular order: first solve constraints for the definition of id
. This will be a -> a
. Then fresh, separate copies of the type of id
can be substituted into the constraints for each place id
is used, for example a2 -> a2
and a3 -> a3
.
That wasn't readily explained in online resources. They'll mention algorithm W or M but not how they work in terms of solving constraints, or why it doesn't barf on let-polymorphism: each of those algorithms enforce an ordering on solving the constraints.
I found this resource extremely helpful to tie Algorithm W, M, and the general concept of constraint generation and solving all together. It's a little dense, but better than many:
http://www.cs.uu.nl/research/techreps/repo/CS-2002/2002-031.pdf
Many of the other papers there are nice too:
http://people.cs.uu.nl/bastiaan/papers.html
I hope that helps clarify a somewhat murky world.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With