This option has you do one written exercise followed by an implementation of a type inference engine.
Consider the type judgments discussed in the textbook. These rules are for an eager language. Consider the lazy version of the language instead. Pay special attention to the typing rules for
For each one, provide a new rule or, if you believe the existing rule does not change, explain why not. (If you believe neither rule changes, you can answer both parts together.) If you believe any other type judgements should change, mention those as well.
Derive type constraints for the following language:
<expr> ::= <num>
| true
| false
| {+ <expr> <expr>}
| {- <expr> <expr>}
| {* <expr> <expr>}
| {iszero <expr>}
| {bif <expr> <expr> <expr>}
| <id>
| {with {<id> <expr>} <expr>}
| {rec {<id> <expr>} <expr>}
| {fun {<id>} <expr>}
| {<expr> <expr>}
| tempty
| {tcons <expr> <expr>}
| {tempty? <expr>}
| {tfirst <expr>}
| {trest <expr>}
The only novelty of this language is that the list operations are now
polymorphic; that is, you can create lists of values of any type.
Note: The right hand side of the rec binding does not have to be a
syntactic function. However, you may assume that the rec-bound identifier only
appears under a {fun ...} in the right hand side of the binding. In
other words, the following expressions are legal:
{rec {f {fun {x} {f x}}}
...}
{rec {f {with {y 4}
{fun {x} {f y}}}}
...}
while the following are not legal:
{rec {f f}
...}
{rec {f {+ 1 f}}
...}
Then, write a function which consumes an expression of this language, and returns a list of constraints (of the type defined in Part II).The correspondence between type constraints and the terms in Part II is as follows:
'number or 'boolean.
'-> and a list of two arguments, or'listof and a list of one argument.gensym returns a unique
identifier on every call.
Implement the unification algorithm. The algorithm should work for a generic term representation, as defined below.
A term is either:
In addition, you will need data types for representing a constraint (a pair of terms) and substitution (a variable and a term). The unification algorithm will consume a list of constraints (as defined in Part I) and produce either a list of substitutions or an error string.
Errors can arise from two situations: when the unification of two terms is impossible, or when the occurs check fails. In both cases, you should return a string with an appropriate error message.
Finally, when comparing variables for equality, use Scheme's built-in
eq? function. For symbols, it behaves exactly as
symbol=?; for other values, it compares them for identity (like
Java's == comparison). We will rely on identical
variables being deemed equivalent by eq? when solving the
constraints generated in the following section.
To infer the type of a program, first parse it, then generate constraints, and finally unify the constraints using the functions written for parts I & II as appropriate. The result will be a list of substitutions; by looking up the subsitution for the entire expression, you can access its type.
To implement this, your code needs to define a function,
infer-type,
which consumes a concrete representation of the program (as given above), and produces
either an error string or a representation of the inferred type.
Represent types concretely as:
<type> ::= number
| boolean
| (listof <type>)
| (<type> -> <type>)
| <string>
where strings are used to represent type variables. For example, the
type of length would be:
((listof "a") -> number)
For a very small amount of extra credit, write a program in this
language for which your algorithm infers the type
("a" -> "b"). You shouldn't
attempt this problem until you've fully completed the assignment.
You do not need to implement an interpreter for this language.