15-816 Linear Logic |
In this homework we implement the core of a linear functional programming language. This implementation may later be used to check derivations produced by our automated theorem prover. You are encouraged to collaborate in groups up to 3 students and hand in one joint implementation.
Type Representation in ML mu a. 1 + a Mu (Plus (One, Var 1)) mu a.mu b.1 + a * b Mu (Mu (Plus (One, Tensor (Var 2, Var 1))))
This representation requires that substitution keeps track of variables, which is quite tricky in general. Fortunately, we only need to substitute closed types for type variables (and later closed terms for term variables), which is much easier than the general case. Still, we need to count how many abstractions we have traversed in order to make sure we are substituting for the right variable. The substitution function for types is provided as an example.
lam u. let w1 # w2 = u in w2 # w1 # uwould be represented as
ULam (LetTensor (Var 1, Tensor (Var 1, Tensor (Var 2, Var 3))))
An additional simplification in the representation is afforded by the omission of types in most places (see type checking below), and the addition of a new construct, let name u : A = M in N. Here are the typing and evaluation rules.
G ; . |- M : A (G,u:A) ; D |- N : C -------------------------------------- G ; D |- let name u : A = M in N : C [M/u] N ==> v --------------------------------- let name u : A = M in N ==> v
It is remarkable that this simple strategy allows us to type check any normal form with minimal type annotations: we only need the types of the variables in the environment, but no labels on abstractions or injections, for example. For terms which are not in normal form, we introduce explicit definitions and label the name of the defined variable. This is the purpose of the let name construct. It is also possible to define a corresponding linear definition construct. The fixpoint construct in this context is viewed as an introduction construct, which means its type label may be omitted as well, while let name u:A = M in N is considered atomic if N is atomic and M in normal.
In the signature top level, we have specifications
exception Error of string val check : Lambda.term * Type.tp -> unit val infer : Lambda.term -> Type.tp
which check a normal term against a type or infer the type of an atomic term, starting from the empty context. Errors during type checking are signalled by raising the Error exception. The kinds of errors which may arise are:
Your function print should only print legal values and then only whatever should be observable. For example, values of functional type or additive pairs should only be shown as an underscore or in the form lam _ and <_,_>, respectively.