22c:054 Team Projects
ML Type Checker (Prolog, Oz)
Implement a type checker for an ML-like language.
The type checker is a predicate TypeOf(E,T)
which,
given
an ML expression E
and
an unbound (Prolog, Oz) variable T,
succeeds binding the type of E to T
if E is well-typed
and
fails otherwise.
Your implementation should be such that TypeOf(E,T)
succeeds even when T is previously bound
to some (possibly) parametric type t,
provided that the computed type of E and
t have a common instance.
The type checker knows the type of the primitive function and
constant symbols,
which are just:
::, nil, hd, tl, +, *, ~, 0, 1, true, false, =, <, >, first, second.
Given an ML expression e over those predefined symbols,
it is able to infer/check the type of e.
ML Type System
Your type checker must follow the types derivation rules and facts that
define the type system of languages such as SML or Miranda.
Such rules and facts are described below,
with the following conventions:
- the letter x stands for an identifier;
- the letter T stands for a type;
- the letter e stands for an expression;
- the letter A stands for a sequence of type assumptions
(a type assumption
is simply a type declaration of the form x : T
for a certain identifier x,
stating that x has type T);
- the notation
A, x : T stands for
the sequence A of type assumptions plus
the additional type assumption x:T;
- the notation
A |- e : T
means that under the type assumptions A,
the expression e has type T.
For a concrete example of the conventions above,
the notation
x1 : real, 
x2 : int, 
x : real->int->real 
|- 
(x x1 x2) : real
for instance, says that
the espression (x x1 x2) has type real
under the assumptions that
x has type real->int->real,
x1 has type real,
and
x2 has type int.
Type Rules
-
Pair Construction:
A |- e1 : T1     A |- e2 : T2
|
-------------------
|
A |- (e1, e2) : T1*T2
|
The expression
(e1, e2) has type T1*T2 under the type assumptions A
if
e1 has type T1 under the type assumptions A
and
e2 has type T2 under the type assumptions A.
-
List Construction:
A |- e1 : T     A |- e2 : T list
|
--------------------
|
A |- e1::e2 : T list
|
The expression
e1::e2 has type T list under the type assumptions A
if
e1 has type T under the type assumptions A
and
e2 has type T list under the type assumptions A.
-
Lambda Abstraction:
A, x:T1 |- e : T2
|
----------------------
|
A |- (fn (x:T1) => e) : T1 -> T2
|
The expression
fn (x:T1) => e has type T1 -> T2 under the type assumptions A
if
e has type T2 under the type assumptions A, x:T1.
-
Function Application:
A |- e2 : T1->T2     A |- e1 : T1
|
-----------------------
|
A |- (e2 e1) : T2
|
The expression
(e2 e1) has type T2 under the type assumptions A
if
e2 has type T1 -> T2 under the type assumptions A,
e1 type T1 under the type assumptions A.
-
Conditional:
A |- e1 : bool     A |- e2 : T     A |- e3 : T
|
-----------------------------
|
A |- (if e1 then e2 else e3) : T
|
The expression
if e1 then e2 else e3 has type T under the type assumptions A
if
e1 has type bool under the type assumptions A
and
e2 and e3 have type T under the type assumptions A.
Type Facts
A type fact is a type assumption
that one always makes in the type system in question.
These are the type facts of our ML-like language,
stating the type of the primitive constant/function symbols
in the language.
0 : int, 
1 : int, 
+ : (int*int) -> int, 
* : (int*int) -> int, 
~ : int -> int,
true : bool, 
false : bool,
= : (T*T) -> bool,
> : (int*int) -> bool, 
< : (int*int) -> bool,
nil : T list, 
hd : T list -> T, 
tl : T list -> T list,
first : T1*T2 -> T1, 
second : T1*T2 -> T2.
(the functions first and second
respectively return the first and the second element of a pair.)
Implementation Details (Prolog)
For simplicity,
conform to the restrictions below on the language.
- Implement only the following types:
- basic types: integers, booleans;
- structured types: lists, functions, pairs.
- Do not allow the sequence notation for lists
(e.g. as in [1,2,3]).
- Do not allow identifier declarations
as in
val <id> = <exp>
or
fun <fname> <args> = <exp>.
- Instead of the syntax (<exp> <exp>),
for function application use the notation
<exp>#<exp>
where _#_ replaces the "apply" operator (_ _);
- Do not allow pattern matching in anonymous functions.
- Always specify the type of the argument of an anonymous functions,
but instead of the ML syntax
fn (<varname>:<vartype>) => <exp>,
use the syntax
fn(var(<varname>,<vartype>), <exp>).
- Instead of the syntax if <exp> then <expr> else <exp>,
use the syntax
if(<exp>,<expr>,<exp>).
- Instead of the syntax <type> -> <type>,
use the syntax <type> arrow <type>.
- Implement type variables simply as Prolog variables
(this way, you'll get type instantiation for free).
According to the conventions above, the ML expression
((fn (n:int) => if n>0 then n+n else 0) (hd [4,1]))
for instance, is written as
fn(var(n,int), if(>#(n,0), +#(n,n), 0))#(hd#(4::1::nil))
Similarly, the ML expression
'a -> 'b*'c -> 'c list
is written as
A arrow B*C arrow C list
If you start your implementation with the Prolog code in this
file,
expressions such as
fn(var(n,int), if(>#(n,0), +#(n,n), 0))#(hd#(4::1::nil))
or
A arrow B*C arrow C list
become legal Prolog terms,
which then can be input directly into Prolog.
This will save you the effort of writing a parser for the language.
Hints:
Be careful with the Lambda Abstraction rule.
 
Implementation Details (Oz)
Same as for the Prolog implementation, except for the following:
- use the Oz notation for terms (i.e., no commas);
- use the syntax p(<expr> <expr>)
instead of (<expr>, <expr>) for ML pairs.
- use the syntax list(<type>)
instead of <type> list for list types.
- use the syntax prod(<type> <type>)
instead of <type>*<type> for product types.
- use the syntax arrow(<type> <type>)
instead of <type> -> <type> for arrow types.
- use the prefix list constructor operator cons
in place of the infix operator ::
(and so write a list such as 1::2::nil
as (cons 1 (cons 2 nil)) instead).
- use the prefix integer operator plus
in place of the infix operator +
(similarly for the other arithmetic operators and for equality);
- Always nest in parentheses successive applications, that is,
input <expr>#<expr>#<expr>
always as (<expr>#<expr>)#<expr>.
Last Updated: Mar 8, 2000