106

Chapter 5

Parametric and Hypothetical Judgments

Many deductive systems employ reasoning from hypotheses. We have seen an example in Section 2.5: a typing derivation of a Mini-ML expression requires assumptions about the types of its free variables. Another example occurs in the system of natural deduction in Chapter 7, where a deduction of the judgment that A ⊃ B is true can be given as a deduction of B is true from the hypothesis A is true. We refer to a judgment that J is derivable under a hypothesis J′ as a hypothetical judgment. Its critical property is that we can substitute a derivation D of J′ for every use of the hypothesis J′ to obtain a derivation which no longer depends on the assumption J′.

Related is reasoning with parameters, which also occurs frequently. The system of natural deduction provides once again a typical example: we can infer that ∀x. A is true if we can show that [a/x]A is true, where a is a new parameter which does not occur in any undischarged hypothesis. Similarly, in the typing rules for Mini-ML we postulate that every variable is declared at most once in a context Γ, that is, in the rule

Γ, x:τ_{1} ▷ e : τ_{2} tp lam

Γ ▷ lam x. e : τ_{1} → τ_{2}

the variable x is new with respect to Γ (which represents the hypotheses of the derivation). This side condition can always be fulfilled by tacitly renaming the bound variable. We refer to a judgment that J is derivable with parameter x as a parametric judgment. Its critical property is that we can substitute an expression t for x throughout a derivation of a parametric judgment to obtain a derivation which no longer depends on the parameter x.

Since parametric and hypothetical judgments are common, it is natural to ask if we can directly support them within the logical framework. The answer is affirmative—the key is the notion of function provided in LF. Briefly, the derivation of a hypothetical judgment is represented by a function which maps a derivation of the hypothesis to a derivation of the conclusion. Applying this function corresponds to substituting a derivation for appeals to the hypothesis. Similarly, the derivation of a parametric judgment is represented by a function which maps an expression to a derivation of the instantiated conclusion. Applying this function corresponds to substituting an expressions for the parameter throughout the parametric derivation.

In the remainder of this chapter we elaborate the notions of parametric and hypothetical judgment and their representation in LF. We also show how to exploit them to arrive at a natural and elegant representation of the proof of type preservation for Mini-ML.

5.1 Closed Expressions

When employing parametric and hypothetical judgments, we must formulate the representation theorems carefully in order to avoid paradoxes. As a simple example, we consider the judgment e Closed which expresses that e has no free variables. Expression constructors which do not introduce any bound variables are treated in a straightforward manner.

\frac{clo z}{z Closed}  e Closed ; clo s ; s e Closed

e_{1} Closed e_{2} Closed

clo pair

⟨e_{1}, e_{2}⟩ Closed

e Closed ; clo fst ; fst e Closed  e Closed ; clo snd ; snd e Closed

e_{1} Closed e_{2} Closed

clo app

e_{1} e_{2} Closed

In order to give a concise formulation of the judgment whenever variables are bound we use hypothetical judgments. For example, in order to conclude that lam x. e is closed, we must show that e is closed under the assumption that x is closed. The hypothesis about x may only be used in the deduction of e, but not elsewhere. Furthermore, in order to avoid confusion between different bound variables with the same name, we would like to make sure that the name x is not already used, that is, the judgment should be parametric in x. The hypothetical judgment that J is derivable from hypotheses J_{1}, …, J_{n} is written as

J_{1} … J_{n} ; ⋮ ; J

The construction of a deduction of a hypothetical judgment should be intuitively clear: in addition to the usual inference rules, we may also use a hypothesis as evidence for a judgment. But we must also indicate where an assumption is discharged, that is, after which point in a derivation it is no longer available. We indicate this by providing a name for the hypothesis J and labelling the inference at which the hypothesis is discharged correspondingly. Similarly, we label the inference at which a parameter is discharged. The remaining inference rules for the judgment e Closed using this notation are given below.

e_{1} Closed  e_{2} Closed  \frac{u}{x Closed} ; ⋮ ; e_{3} Closed
clo case^{x,u}
(case e_{1} of z ⇒ e_{2} | s x ⇒ e_{3}) Closed

\frac{u}{x Closed} ; ⋮ ; e Closed

clo lam^{x,u}

lam x. e Closed

e_{1} Closed  \frac{u}{x Closed} ; ⋮ ; e_{2} Closed
clo letv^{x,u}
let val x = e_{1} in e_{2} Closed

e_{1} Closed  \frac{u}{x Closed} ; ⋮ ; e_{2} Closed
clo letn^{x,u}
let name x = e_{1} in e_{2} Closed

\frac{u}{x Closed} ; ⋮ ; e Closed

clo fix^{x,u}

fix x. e Closed

In order to avoid ambiguity we assume that in a given deduction, all labels for the inference rules clo case, clo lam, clo letv, clo letn and clo fix are distinct. An alternative to this rather stringent, but convenient requirement is suggestive of the representation of hypothetical judgments in LF: we can think of a label u as a variable ranging over deductions. The variable is bound by the inference which discharges the hypothesis.

The following derivation shows that the expression let name f = lam x. x in f (f z) is closed.

\frac{u}{x Closed}

clo lam^{x,u}

lam x. x Closed

\frac{w}{f Closed}

\frac{w}{f Closed}  \frac{clo z}{z Closed}

clo app

f z Closed

clo app

f (f z) Closed

clo letn^{f,w}

let name f = lam x. x in f (f z) Closed

This deduction has no undischarged assumptions, but it contains subderivations with hypotheses. The right subderivation, for example, would traditionally be written as

f Closed  f Closed  \frac{clo z}{z Closed} clo app f z Closed
clo app.
f (f z) Closed

In this notation we can not determine if there are two hypotheses (which happen to coincide) or two uses of the same hypothesis. This distinction may be irrelevant under some circumstances, but in many situations it is critical. Therefore we retain the labels even for hypothetical derivations, with the restriction that the free labels must be used consistently, that is, all occurrences of a label must justify the same hypothesis. The subderivation above then reads

\frac{w}{f Closed}

\frac{w}{f Closed}  \frac{clo z}{z Closed}

clo app

f z Closed

clo app.
f (f z) Closed

There are certain reasoning principles for hypothetical derivations which are usually not stated explicitly. One of them is that hypotheses need not be used. For example, lam x. z is closed as witnessed by the derivation

\frac{clo z}{z Closed}

clo lam^{x,u}

lam x. z Closed

which contains a subdeduction of z Closed from hypothesis u :: x Closed. Another principle is that hypotheses may be used more than once and thus, in fact, arbitrarily often. Finally, the order of the hypotheses is irrelevant (although their labelling is not). This means that a hypothetical deduction in this notation could be evidence for a variety of hypothetical judgments which differ in the order of the hypotheses or may contain further, unused hypotheses. One can make these principles explicit as inference rules, in which case we refer to them as weakening (hypotheses need not be used), contraction (hypotheses may be used more than once), and exchange (the order of the hypotheses is irrelevant). We should keep in mind that if these principles do not apply then the judgment should not be considered to be hypothetical in the usual sense, and the techniques below may not apply. These properties have been studied abstractly as consequence relations [Avr87, Gar92].

The example derivation above is not only hypothetical in w, but also parametric in f, and we can therefore substitute an expression such as lam x. x for f and obtain another valid deduction.

\frac{w}{lam x. x Closed}

\frac{w}{lam x. x Closed}  \frac{clo z}{z Closed}

clo app

(lam x. x) z Closed

clo app

(lam x. x) ((lam x. x) z) Closed

If C :: e Closed is parametric in x, then we write [e′/x]C :: [e′/x]e Closed for the result of substituting e′ for x in the deduction C. In the example, the deduction still depends on the hypothesis w, which suggests another approach to understanding hypothetical judgments. If a deduction depends on a hypothesis u :: J we can substitute any valid deduction of J for this hypothesis to obtain another deduction which no longer depends on u :: J. Let C be the deduction above and let C′ :: lam x. x Closed be

\frac{u}{x Closed}

clo lam^{x,u}.

lam x. x Closed

Note that this deduction is not parametric in x, that is, x must be considered a bound variable within C′. The result of substituting C′ for w in C is

\frac{u}{x Closed}

clo lam^{x,u}

lam x. x Closed

\frac{u′}{x′ Closed}

clo lam^{x′,u′}
lam x′. x′ Closed

\frac{clo z}{z Closed}

clo app

(lam x′. x′) z Closed

clo app

(lam x. x) ((lam x′. x′) z) Closed

where we have renamed some occurrences of x and u in order to satisfy our global side conditions on parameter names. In general we write [D′/u]D for the result of substituting D′ for the hypothesis u :: J′ in D, where D′ :: J′. During substitution we may need to rename parameters or labels of assumptions to avoid violating side conditions on inference rules. This is analogous to the renaming of bound variables during substitution in terms in order to avoid variable capture.

The representation of the judgment e Closed in LF follows the judgment-as-types principle: we introduce a type family ‘closed’ indexed by an expression.

closed : exp → type

The inference rules that do not employ hypothetical judgments are represented straightforwardly.

clo z  : closed z
clo s  : ΠE:expclosed E → closed (s E)
clo pair : ΠE_{1}:exp. ΠE_{2}:exp.
closed E_{1} → closed E_{2} → closed (pair E_{1} E_{2})
clo fst : ΠE:exp. closed E → closed (fst E)
clo snd : ΠE:exp. closed E → closed (snd E)
clo app : ΠE_{1}:exp. ΠE_{2}:exp.
closed E_{1} → closed E_{2} → closed (app E_{1} E_{2})

Now we reconsider the rule clo lam.

\frac{u}{x Closed} ; ⋮ ; e Closed

clo lam^{x,u}

lam x. e Closed

The judgment in the premiss is parametric in x and hypothetical in x Closed. We thus consider it as a function which, when applied to an e′ and a deduction C :: e′ Closed yields a deduction of [e′/x]e Closed.

clo lam : ΠE:exp → exp.

(Πx:exp. closed x → closed (E x)) → closed (lam E)

Recall that it is necessary to represent the scope of a binding operator in the language of expressions as a function from expressions to expressions. Similar declarations are necessary for the hypothetical judgments in the premisses of the clo letv, clo letn, and clo fix rules.

clo case : ΠE_{1}:exp. ΠE_{2}:exp. ΠE_{3}:exp → exp.

closed E_{1} → closed E_{2}

→ (Πx:exp. closed x → closed (E_{3} x))

→ closed (case E_{1} E_{2} E_{3})

clo letv : ΠE_{1}:exp. ΠE_{2}:exp → exp.

closed E_{1} → (Πx:exp. closed x → closed (E_{2} x))

→ closed (letv E_{1} E_{2})

clo letn : ΠE_{1}:exp. ΠE_{2}:exp → exp.

closed E_{1} → (Πx:exp. closed x → closed (E_{2} x))

→ closed (letn E_{1} E_{2})

clo fix : ΠE:exp → exp.

(Πx:exp. closed x → closed (E x)) → closed (fix E)

We refer to the signature which includes expression constructors, the declarations of the family closed and the encodings of the inference rules above as EC.

In order to appreciate how this representation works, it is necessary to understand the representation function ⌜·⌝ on deductions. As usual, the definition of the representation function follows the structure of C :: e Closed. We only show a few typical cases.

Case: C = \begin{matrix}C_{1} \\ e_{1} Closed \end{matrix} \begin{matrix}C_{2} \\ e_{2} Closed \end{matrix} clo app e_{1} e_{2} Closed. Then

⌜C⌝ = clo app ⌜e_{1}⌝ ⌜e_{2}⌝ ⌜C_{1}⌝ ⌜C_{2}⌝.

Case: C = \frac{u}{x Closed} ; C_{1} ; e Closed clo lam^{x.u} lam x. e Closed. Then

⌜C⌝ = clo lam (λx:exp. ⌜e⌝) (λx:exp. λu:closed x. ⌜C_{1}⌝).

Case: C = \frac{u}{x Closed}. Then

⌜C⌝ = u.

The example deduction above which is evidence for the judgment let name f = lam x. x in f z Closed is represented as

⊢_{EC} clo letn (lam (λx:exp. x)) (λf:exp. app f z)

(clo lam (λx:exp. x) (λx:exp. λu:closed u. u)) (λf:exp. λw:closed f. clo app f z w clo z) : closed (letn (lam (λx:exp. x)) (λf:exp. app f z))

To show that the above is derivable we need to employ the rule of type conversion as in the example on page 59. The naive formulation of the soundness of the representation does not take the hypothetical or parametric judgments into account.

Property 5.1 (Soundness of Representation, Version 1) Given any deduction C :: e Closed. Then ⊢_{EC} ⌜C⌝ ⇑ closed ⌜e⌝.

While this indeed a theorem, it cannot be proven directly by induction—the induction hypothesis will not be strong enough to deal with the inference rules whose premisses require deductions of hypothetical judgments. In order to formulate and prove a more general property we have to consider the representation of hypothetical judgments. As one can see from the example above, the deduction fragment

\frac{w}{f Closed}  \frac{clo z}{z Closed}

clo app

f z Closed

is represented by the LF object

clo app f z w clo z

which is valid in the context with the declarations f:exp and w:closed f. In order to make the connection between the hypotheses and the LF context explicit, we retain the labels of the assumptions and explicitly define the representation of a list of hypotheses. Let Δ = u_{1} :: x_{1} Closed, …, u_{n} :: x_{n} Closed be a list of hypotheses where all labels are distinct. Then

⌜Δ⌝ = x_{1}:exp, u_{1}:closed x_{1}, …, x_{n}:exp, u_{n}:closed x_{n}.

The reformulated soundness property now references the available hypotheses.

Property 5.2 (Soudness of Representation, Version 2) Given any deduction C :: e Closed from hypotheses Δ = u_{1} :: x_{1} Closed, …, u_{n} :: x_{n} Closed. Then ⊢_{EC} ⌜Δ⌝ Ctx and

⌜Δ⌝ ⊢_{EC} ⌜C⌝ ⇑ closed ⌜e⌝.

Proof: The proof is by induction on the structure of C. We show three typical cases. Case:

C = \begin{matrix}C_{1} \\ e_{1} Closed \end{matrix} \begin{matrix}C_{2} \\ e_{2} Closed \end{matrix} clo app. e_{1} e_{2} Closed

Since C is a deduction from hypotheses Δ, both C_{1} and C_{2} are also deductions from hypotheses Δ. From the induction hypothesis we conclude then that 5.1. CLOSED EXPRESSIONS 115

1. ⌜Δ⌝ ⊢_{EC} ⌜C_{1}⌝ ⇑ closed ⌜e_{1}⌝, and

2. ⌜Δ⌝ ⊢_{EC} ⌜C_{2}⌝ ⇑ closed ⌜e_{2}⌝ are both derivable. Thus, from the type of clo app,

⌜Δ⌝ ⊢_{EC} clo app ⌜e_{1}⌝ ⌜e_{2}⌝ ⌜C_{1}⌝ ⌜C_{2}⌝ ⇑ closed (app ⌜e_{1}⌝ ⌜e_{2}⌝). It remains to notice that ⌜e_{1} e_{2}⌝ = app ⌜e_{1}⌝ ⌜e_{2}⌝.

Case:

C = \frac{u}{x Closed} ; C_{1} ; e Closed clo lam^{x,u}. lam x. e Closed

Then C_{1} is a deduction from hypotheses Δ, u :: x Closed, and

⌜Δ, u :: x Closed⌝ = ⌜Δ⌝, x:exp, u:closed x.

By the induction hypothesis on C_{1} we thus conclude that

⌜Δ⌝, x:exp, u:closed x ⊢_{EC} ⌜C_{1}⌝ ⇑ closed ⌜e⌝

is derivable. Hence, by two applications of the canpi rule for canonical forms,

⌜Δ⌝ ⊢_{EC} λx:exp. λu:closed x. ⌜C_{1}⌝ ⇑ Πx:exp. Πu:closed x. closed ⌜e⌝ is also derivable. By the representation theorem for expressions (Theorem 3.6) and the weakening for LF we also know that

⌜Δ⌝ ⊢_{EC} λx:exp. ⌜e⌝ ⇑ exp → exp

is derivable. From this and the type of clo lam we infer

⌜Δ⌝ ⊢_{EC} clo lam (λx:exp. ⌜e⌝)
↓ (Πx:exp. Πu:closed x. closed ((λx:exp. ⌜e⌝) x))
→ closed (lam (λx:exp. ⌜e⌝)).

By the rule atmcnv, using one β-conversion in the type above, we conclude

⌜Δ⌝ ⊢_{EC} clo lam (λx:exp. ⌜e⌝)
↓ (Πx:exp. Πu:closed x. closed ⌜e⌝)
→ closed (lam (λx:exp. ⌜e⌝)).

Using the rules atmapp and cancon which are now applicable we infer

⌜Δ⌝ ⊢_{EC} clo lam (λx:exp. ⌜e⌝) (λx:exp. λu:closed x. ⌜C_{1}⌝)
⇑ closed (lam (λx:exp. ⌜e⌝)),

which is the desired conclusion since ⌜lam x. e⌝ = lam (λx:exp. ⌜e⌝).

Case:

C = \frac{u.}{x Closed}

Then ⌜C⌝ = u, to which ⌜Δ⌝ assigns type closed x = closed ⌜x⌝, which is what we needed to show.

□

The inverse of the representation function, ⌞·⌟, is defined on canonical objects C of type closed E for some E of type exp. This is sufficient for the adequacy theorem below—one can extend it to arbitrary valid objects via conversion to canonical form. Again, we only show three critical cases.

⌞clo app E_{1} E_{2} C_{1} C_{2}⌟ =

⌞C_{1}⌟ ⌞C_{2}⌟ clo app

⌞E_{1}⌟ ⌞E_{2}⌟ Closed

⌞clo lam (λx:exp. E) (λx:exp. λu:closed x. C_{1})⌟ =

\frac{u}{x Closed} ; ⌞C_{1}⌟ ; ⌞E⌟ Closed
clo lam^{x,u}
lam x. ⌞E⌟ Closed

⌞u⌟ = \frac{u}{x Closed}

The last case reveals that the inverse of the representation function should be parameterized by a context so we can find the x which is assumed to be closed according to hypothesis u. Alternatively, we can assume that we always know the type of the canonical object we are translating to a deduction. Again, we are faced with the problem that the natural theorem regarding the function ⌞·⌟ cannot be proved directly by induction.

Property 5.3 (Completeness of Representation, Version 1) Given LF objects E and C such that ⊢_{EC} E ⇑ exp and ⊢_{EC} C ⇑ closed E. Then ⌞C⌟ :: ⌞E⌟ Closed.

In order to prove this, we generalize it to allow appropriate contexts. These contexts need to be translated to an appropriate list of hypotheses. Let Γ be a context of the form x_{1}:exp, u_{1}:closed x_{1}, …, x_{n}:closed x_{n}. Then ⌞Γ⌟ is the list of hypotheses u_{1} :: x_{1} Closed, …, u_{n} :: x_{n}Closed.

Property 5.4 (Completeness of Representation, Version 2) Given a context Γ = x_{1}:exp, u_{1}:closed x_{1}, …, x_{n}:closed x_{n} and LF objects E and C such that Γ ⊢_{EC} E ⇑ exp and Γ ⊢_{EC} C ⇑ closed E. Then ⌞C⌟ :: ⌞E⌟ Closed is a valid deduction from hypotheses ⌞Γ⌟. Moreoever, ⌞⌜C⌝⌟ = C and ⌞⌜Δ⌝⌟ = Δ for deductions C :: e Closed and hypotheses Δ.

Proof: By induction on the structure of the derivation Γ ⊢_{EC} C ⇑ closed E. The restriction to contexts Γ of a certain form is crucial in this proof (see Exercise 5.1). □

The usual requirement that ⌜·⌝ be a compositional bijection can be understood in terms of substitution for deductions. Let C :: e Closed be a deduction from hypothesis u :: x closed. Then compositionality of the representation function requires

⌜[C′/u][e′/x]C⌝ = [⌜C′⌝/u][⌜e′⌝/x]⌜C⌝

whenever C′ :: e′ Closed. Note that the substitution on the left-hand side is substitution for undischarged hypotheses in a deduction, while substitution on the right is at the level of LF objects. Deductions of parametric and hypothetical judgments are represented as functions in LF. Applying such functions means to substitute for deductions, which can be exhibited if we rewrite the right-hand side of the equation above, preserving definitional equality.

[⌜C′⌝/u][⌜e′⌝/x]⌜C⌝ ≡ (λx:exp. λu:closed x. ⌜C⌝) ⌜e′⌝ ⌜C′⌝

The discipline of dependent function types ensures the validity of the object on the right-hand side:

(λx:exp. λu:closed x. ⌜C⌝) ⌜e′⌝ : [⌜e′⌝/x](closed x → closed ⌜e⌝).

Hence, by compositionality of the representation for expressions,

(λx:exp. λu:closed x. ⌜C⌝) ⌜e′⌝ : closed ⌜e′⌝ → closed ⌜[e′/x]e⌝

and the application of this object to ⌜C′⌝ is valid and of the appropriate type.

Theorem 5.5 (Adequacy) There is a bijection between deductions C :: e Closed from hypotheses u_{1} :: x_{1} Closed, …, u_{n} :: x_{n} Closed and LF objects C such that

x_{1}:exp, u_{1}:closed x_{1}, …, x_{n}:exp, u_{n}:closed x_{n} ⊢_{EC} C ⇑ closed ⌜e⌝.

The bijection is compositional in the sense that for expressions e_{1}, …, e_{n} and deductions C_{1} :: e_{1} Closed, …, C_{n} :: e_{n} Closed,

⌜[C_{n}/u_{n}][e_{n}/x_{n}] … [C_{1}/u_{1}][e_{1}/x_{1}]C⌝ = [⌜C_{n}⌝/u_{n}][⌜e_{n}⌝/x_{n}] … [⌜C_{1}⌝/u_{1}][⌜e_{1}⌝/x_{1}]⌜C⌝

where we assume that the free variables of e_{1}, …, e_{n} and C_{1}, …, C_{n} are distinct from x_{1}, …, x_{n} and u_{1}, …, u_{n}.

Proof: Properties 5.2 and 5.4 show the existence of the bijection. To show that it is compositional we reason by induction over the structure of C (see Exercise 5.2). □

5.2 Function Types as Goals in Elf

Below we give the transcription of the LF signature above in Elf.

closed : exp -> type.  %name closed u
% Natural Numbers
clo_z     : closed z.
clo_s     : closed (s E)
             <- closed E.
clo_case  : closed (case E1 E2 E3)
             <- closed E1
             <- closed E2
             <- ({x:exp} closed x -> closed (E3 x)).
% Pairs
clo_pair : closed (pair E1 E2)
              <- closed E1
              <- closed E2.
clo_fst  : closed (fst E)
              <- closed E.
clo_snd  : closed (snd E)
              <- closed E.
% Functions
clo_lam : closed (lam E)
             <- ({x:exp} closed x -> closed (E x)).
clo_app : closed (app E1 E2)
             <- closed E1
             <- closed E2.

  % Definitions
  clo_letv : closed (letv E1 E2)
                <- closed E1
                <- ({x:exp} closed x -> closed (E2 x)).
  clo_letn : closed (letn E1 E2)
                <- closed E1
                <- ({x:exp} closed x -> closed (E2 x)).
  % Recursion
  clo_fix : closed (fix E)
               <- ({x:exp} closed x -> closed (E x)).

Note that we have changed the order of arguments as in other examples. It seems reasonable to expect that this signature could be used as a program to determine if a given object e of type exp is closed. Let us consider the subgoals as they arise in a query to check if lam y. y is closed.

  ?- closed (lam [y:exp] y).
  % Resolved with clause clo_lam
  ?- {x:exp} closed x -> closed (([y:exp] y) x).

Recall that solving a goal means to find a closed expression of the query type. Here, the query type is a (dependent) function type. From Theorem 3.13 we know that if a closed object of type Πx:A. B exists, then there is a definitionally equal object of the form λx:A. M such that M has type B in the context augmented with the assumption x:A. It is thus a complete strategy in this case to make the assumption that x has type exp and solve the goal

  ?- closed x -> closed (([y:exp] y) x).

However, x now is not a free variable in same sense as V in the query

  ?- eval (lam [y:exp] y) V.

since it is not subject to instantiation during unification. In order to distinguish these different kinds of variables, we call variables which are subject to instantiation logic variables and variables which act as constants to unification parameters. In order to clarify the roles of variables, the Elf printer prefixes parameters with an exclamation mark !. Thus the current goal might be presented as

  !x : exp
  ?- closed !x -> closed (([y:exp] y) !x).

Here we precede the query with the typings for the current parameters. Now recall that A -> B is just a concrete syntax for {_:A} B where _is an anonymous variable which cannot appear free in B. Thus, this case is handled similarly: we introduce a new parameter u of type closed !x and then solve the subgoal

  !x : exp
  !u : closed !x
  ?- closed (([y:exp] y) !x).

By an application of β-conversion this is transformed into the equivalent goal

  !x : exp
  !u : closed !x
  ?- closed !x.

Now we can use the parameter !u as the requested object of type closed !x and the query succeeds without further subgoals.

We now briefly consider, how the appropriate closed object of the original query, namely ?- closed (lam [y:exp] y). would be constructed. Recall that if Γ, x:A ⊢_{Σ} M : B then Γ ⊢_{Σ} λx:A. M : Πx:A. B. Using this we can now through the trace of the search in reverse, constructing inhabiting objects as we go along and inserting conversions where necessary.

                     !u : closed !x.
        [u:closed !x] u : closed !x -> closed !x.
  [x:exp][u:closed x] u : {x:exp} closed x -> closed x.
  [x:exp][u:closed x] u : {x:exp} closed x -> closed (([y:exp] y) x).
  clo_lam ([x:exp][u:closed x] u)
                        : closed (lam [y:exp] y).

Just as in Prolog, search proceeds according to a fixed operational semantics. This semantics specifies that clauses (that is, LF constant declarations) are tried in order from the first to the last. Before referring to the fixed signature, however, the temporary hypotheses are consulted, always considering the most recently introduced parameter first. After all of them have been considered, then the current signature is traversed. In this example the search order happens to be irrelevant as there will always be at most one assumption available for any expression parameter.

The representations of parametric and hypothetical judgments can also be given directly at the top-level. Here are two examples: the first to find the representation of the hypothetical deduction of f (f z) closed from the hypothesis f closed, the second to illustrate failure when given an expression (⟨x, x⟩) which is not closed.

  ?- Q : {f:exp} closed f -> closed (app f (app f z)).
  Q = [f:exp] [u:closed f] clo_app (clo_app clo_z u) u.

  ;
  no more solutions
  ?- Q : {x:exp} closed (pair x x).
  no

Note that the quantification on the variable x is necessary, since the query ?- closed (pair x x). is considered to contain an undeclared constant x (which is an error), and the query ?- closed (pair X X) considers X as a logic variable subject to instantation:

  ?- Q : closed (pair X X).
  Solving...
  X = z,
  Q = clo_pair clo_z clo_z.
  ;
  X = s z,
  Q = clo_pair (clo_s clo_z) (clo_s clo_z).
  yes

5.3 Negation

Now that we have seen how to write a program to detect closed expressions, how do we write a program which succeeds if an expression is not closed? In Prolog, one has the possibility of using the unsound technique of negation-as-failure to write a predicate which succeeds if and only if another predicate fails finitely. In Elf, this technique is not available. Philosophically one might argue that the absence of evidence for e Closed does not necessarily mean that e is not closed. More pragmatically, note that if we possess evidence that e is closed, then this will continue to be evidence regardless of any further inference rules or hypotheses we might introduce to demonstrate that expressions are closed. However, the judgment that ⟨x, x⟩ is not closed does not persist if we add the hypothesis that x is closed. Only under a so-called closed-world assumption, that is, the assumption that no further hypotheses or inference rules will be considered, is it reasonable to conclude the ⟨x, x⟩ is not closed. The philosophy behind the logical framework is that we work with an implicit open-world assumption, that is, all judgments, once judged to be evident since witnessed by a deduction, should remain evident under extensions of the current rules of inference. Note that this is clearly not the case for the meta-theorems we prove. Their proofs rely on induction on the structure of derivations and they may no longer be valid when further rules are added.

Thus it is necessary to explicitly define a judgment e Open to provide means for giving evidence that e is open, that is, it contains at least one free variable. Below is the implementation of such a judgment in Elf.

open : exp -> type.  %name open v
% Natural Numbers
open_s     : open (s E) <- open E.
open_case1 : open (case E1 E2 E3) <- open E1.
open_case2 : open (case E1 E2 E3) <- open E2.
open_case3 : open (case E1 E2 E3) <- ({x:exp} open (E3 x)).
% Pairs
open_pair1 : open (pair E1 E2) <- open E1.
open_pair2 : open (pair E1 E2) <- open E2.
open_fst   : open (fst E) <- open E.
open_snd   : open (snd E) <- open E.
% Functions
open_lam  : open (lam E) <- ({x:exp} open (E x)).
open_app1 : open (app E1 E2) <- open E1.
open_app2 : open (app E1 E2) <- open E2.
% Definitions
open_letv1 : open (letv E1 E2) <- open E1.
open_letv2 : open (letv E1 E2) <- ({x:exp} open (E2 x)).
open_letn1 : open (letn E1 E2) <- open E1.
open_letn2 : open (letn E1 E2) <- ({x:exp} open (E2 x)).
% Recursion
open_fix : open (fix E) <- ({x:exp} open (E x)).

One curious fact about this judgment is that there is no base case, that is, without any hypotheses any query of the form ?- open ⌜e⌝. will fail! That is, with a given query we must provide evidence that any parameters which may occur in it are open. For example,

?- Q : {x:exp} open (pair x x).
no
?- Q : {x:exp} open x -> open (pair x x).
Q = [x:exp] [v:open x] open_pair1 v.
;

  solved
  Q = [x:exp] [v:open x] open_pair2 v.
  ;
  no more solutions
  ?- Q : {x:exp} open x -> open (lam [x:exp] pair x x).
  no

5.4 Representing Mini-ML Typing Derivations

In this section we will show a natural representation of Mini-ML typing derivations in LF. In order to avoid confusion between the contexts of LF and the contexts of Mini-ML, we will use Δ throughout the remainder of this chapter to designate a Mini-ML context. The typing judgment of Mini-ML then has the form

Δ ▷ e : τ

and expresses that e has type τ in context Δ. We observe that this judgment can be interpreted as a hypothetical judgment with hypotheses Δ. There is thus an alternative way to describe the judgment e : τ which employs hypothetical judgments without making assumptions explicit.

Δ, x:τ_{1} ▷ e : τ_{2} tp lam

Δ ▷ lam x. e : τ_{1} → τ_{2}

\frac{u}{▷ x : τ_{1}} ; ⋮ ; ▷ e : τ_{2}

tp lam^{x,u}
▷ lam x. e : τ_{1} → τ_{2}

The judgment in the premiss in the formulation of the rule on the right is parametric in x and hypothetical in u :: x:τ_{1}. On the left, all available hypothesis are represented explicitly. The restriction that each variable may be declared at most once in a context and bound variables may be renamed tacitly encodes the parametricity with respect to x.

First, however, the representation of Mini-ML types. We declare an LF type constant, tp, for the representation of Mini-ML types. Recall, from Section 2.5,

Types τ ::= nat | τ_{1} × τ_{2} | τ_{1} → τ_{2} | α

It is important to bear in mind that → is overloaded here, since it stands for the function type constructor in Mini-ML and in LF. It should be clear from the context which constructor is meant in each instance. The representation function and the LF declarations are straightforward.

tp : type
⌜α⌝ = α
⌜nat⌝ = nat  nat : tp
⌜τ_{1} × τ_{2}⌝ = cross ⌜τ_{1}⌝ ⌜τ_{2}⌝  cross : tp → tp → tp
⌜τ_{1} → τ_{2}⌝ = arrow ⌜τ_{1}⌝ ⌜τ_{2}⌝  arrow : tp → tp → tp

Here α on the right-hand side stands for a variable named α in the LF type theory. We refer to the signature in the right-hand column as T. We briefly state (without proof) the representation theorem.

Theorem 5.6 (Adequacy) The representation function ⌜·⌝ is a compositional bijection between Mini-ML types and canonical LF objects of type tp over the signature T.

Now we try to apply the techniques for representing hypothetical judgments developed in Section 5.1 to the representation of the typing judgment (for an alternative, see Exercise 5.6). The representation will be as a type family ‘of’ such that

⌜Δ⌝ ⊢ ⌜P⌝ : of ⌜e⌝ ⌜τ⌝

whenever P is a deduction of Δ ▷ e : τ. Thus,

of : exp → tp → type

with the representation for Mini-ML contexts Δ as LF contexts ⌜Δ⌝.

\begin{aligned}⌜·⌝ & = & · \\ ⌜Δ, x:τ⌝ & = & ⌜Δ⌝, x:exp, u:of x ⌜τ⌝ \end{aligned}

Here u must be chosen to be different from x and any other variable in ⌜Δ⌝ in order to satisfy the general assumption about LF contexts. This assumption can be satisfied since we made a similar assumption about Δ.

For typing derivation themselves, we only show three critical cases in the definition of ⌜P⌝ for P :: Δ ▷ e : τ. The remainder is given directly in Elf later. The type family of : exp → tp → type represents the judgment e : τ.

Case:

P = \begin{matrix}P_{1} \\ Δ ▷ e_{1} : τ_{2} → τ_{1} \end{matrix} \begin{matrix}P_{2} \\ Δ ▷ e_{2} : τ_{2} \end{matrix} tp app. Δ ▷ e_{1} e_{2} : τ_{1}

In this simple case we let

⌜P⌝ = tp app ⌜τ_{1}⌝ ⌜τ_{2}⌝ ⌜e_{1}⌝ ⌜e_{2}⌝ ⌜P_{1}⌝ ⌜P_{2}⌝

where

tp app : ΠT_{1}:tp. ΠT_{2}:tp. ΠE_{1}:exp. ΠE_{2}:exp
of E_{1} (arrow T_{2} T_{1}) → of E_{2} T_{2} → of (app E_{1} E_{2}) T_{1}

Case:

P = \begin{matrix}P′ \\ Δ, x:τ_{1} ▷ e : τ_{2} \end{matrix} tp lam. Δ ▷ lam x. e : τ_{1} → τ_{2}

In this case we view P′ as a deduction of a hypothetical judgment, that is, a derivation of e : τ_{2} from the hypothesis x:τ_{1}. We furthermore note that P′ is parametric in x and choose an appropriate functional representation.

⌜P⌝ = tp lam ⌜τ_{1}⌝ ⌜τ_{2}⌝ (λx:exp. ⌜e⌝) (λx:exp. λu:of x ⌜τ_{1}⌝. ⌜P′⌝)

The constant tp lam must thus have the following type:

tp lam : ΠT_{1}:tp. ΠT_{2}:tp. ΠE:exp → exp.
(Πx:exp. of x T_{1} → of (E x) T_{2})
→ of (lam E) (arrow T_{1} T_{2}).

Representation of a deduction P with hypotheses Δ requires unique labels for the various hypotheses, in order to return the appropriate variable whenever an hypothesis is used. While we left this correspondence implicit, it should be clear that in the case of ⌜P′⌝ above, the hypothesis x:τ_{1} should be considered as labelled by u.

Case:

P = \begin{aligned}Δ(x) = τ \\ & tp var. \\ Δ ▷ x : τ \end{aligned}

This case is not represented using a fixed inference rule, but we will have a variable u of type ‘of x ⌜τ⌝’ which implicitly provides a label for the assumption x. We simply return this variable.

⌜P⌝ = u

The adequacy of this representation is now a straightforward exercise, given in the following two properties. We refer to the full signature (which includes the signatures T for Mini-ML types and E for Mini-ML expressions) as T D.

Property 5.7 (Soundness) If P :: Δ ▷ e : τ then ⊢_{T D} ⌜Δ⌝ Ctx and

⌜Δ⌝ ⊢_{T D} ⌜P⌝ ⇑ of ⌜e⌝ ⌜τ⌝.

Property 5.8 (Completeness) Let Δ be a Mini-ML context and Γ = ⌜Δ⌝. If ⊢_{T D} E ⇑ exp, ⊢_{T D} T ⇑ tp, and

Γ ⊢_{T D} P ⇑ of E T

then there exist e, τ, and a derivation P :: Δ ▷ e : τ such that ⌜e⌝ = E, ⌜T ⌝ = τ, and ⌜P⌝ = P.

It remains to understand the compositionality property of the bijection. We reconsider the substitution lemma (Lemma 2.4):

If Δ ▷ e_{1} : τ_{1} and Δ, x_{1}:τ_{1} ▷ e_{2} : τ_{2} then Δ ▷ [e_{1}/x_{1}]e_{2} : τ_{2}.

The proof is by induction on the structure of P_{2} :: (Δ, x_{1}:τ_{1} ▷ e_{2} : τ_{2}). Wherever the assumption x_{1}:τ_{1} is used, we substitute a version of the derivation P_{1} :: Δ ▷ e_{1} : τ_{1} where some additional (and unused) typing assumptions may have been added to Δ. A reformulation using the customary notation for hypothetical judgments exposes the similarity to the considerations for the judgment e Closed considered in Section 5.1.

If \frac{u_{1}}{▷ x_{1}:τ_{1}} ; P_{2} ; ▷ e_{2}:τ_{2}and \begin{matrix}P_{1} \\ ▷ e_{1}:τ_{1} \end{matrix} then \begin{aligned}P_{1} \\ & u_{1} \\ ▷ e_{1}:τ_{1} \end{aligned} ; [e_{1}/x_{1}]P_{2} ; ▷ [e_{1}/x_{1}]e_{2} : τ_{2}

Here, [e_{1}/x_{1}]P_{2} is the substitution of e_{1} for x_{1} in the deduction P_{2}, which is legal since P_{2} is a deduction of a judgment parametric in x_{1}. Furthermore, the deduction P_{1} has been substituted for the hypotheses labelled u in P_{2}, indicated by writing P_{1} above the appropriate hypothesis. Using the conventions established for hypothetical and parametric judgments, the final deduction above can also be written as [P_{1}/u_{1}][e_{1}/x_{1}]P_{2}. Compositionality of the representation then requires

\begin{aligned}⌜[P_{1}/u_{1}][e_{1}/x_{1}]P_{2}⌝ & = & [⌜P_{1}⌝/u_{1}][⌜e_{1}⌝/x_{1}]⌜P_{2}⌝ \\ & ≡ & (λx_{1}:exp. λu_{1}:of x_{1} ⌜τ_{1}⌝. ⌜P_{2}⌝) ⌜e_{1}⌝ ⌜P_{1}⌝ \end{aligned}

After appropriate generalization, this is proved by a straightforward induction over the structure of P_{2}, just as the substitution lemma for typing derivation which lies at the heart of this property.

Theorem 5.9 (Adequacy) There is a bijection between deductions

\begin{matrix}P \\ x_{1}:τ_{1}, …, x_{n}:τ_{n} ▷ e : τ \end{matrix}

and LF objects P such that

x_{1}:exp, u_{1}:of x_{1} ⌜τ_{1}⌝, …, x_{n}:exp, u_{n}:of x_{n} ⌜τ_{n}⌝ ⊢_{T D} P ⇑ of ⌜e⌝ ⌜τ⌝.

The bijection is compositional in the sense that for expressions e_{1}, …, e_{n} and deductions P_{1} :: Δ′ ▷ e_{1} : τ_{1}, …, P_{n} :: Δ′ ▷ e_{n} : τ_{n} whose free variables are distinct from x_{1}, …, x_{n} and u_{1}, …, u_{n} we have

⌜[P_{n}/u_{n}][e_{n}/x_{n}] … [P_{1}/u_{1}][e_{1}/x_{1}]P⌝ = [⌜P_{n}⌝/u_{n}][⌜e_{n}⌝/x_{n}] … [⌜P_{1}⌝/u_{1}][⌜e_{1}⌝/x_{1}]⌜P⌝

where u_{1}, …, u_{n} are labels for the assumptions x_{1}:τ_{1}, …, x_{n}:τ_{n}, respectively.

Proof: [ to be sketched ]

□

5.5 An Elf Program for Mini-ML Type Inference

We now complete the signature from the previous section by transcribing the rules from the previous section and Section 2.5 into Elf. The notation will be suggestive of a reading of this signature as a program for type inference. First, the declarations of Mini-ML types.

  tp : type.  %name tp T
  nat   : tp.
  cross : tp -> tp -> tp.
  arrow : tp -> tp -> tp.

Next, the typing rules.

  of : exp -> tp -> type.  %name of P
  % Natural Numbers
  tp_z     : of z nat.
  tp_s     : of (s E) nat
              <- of E nat.
  tp_case  : of (case E1 E2 E3) T
              <- of E1 nat
              <- of E2 T

            <- ({x:exp} of x nat -> of (E3 x) T).
% Pairs
tp_pair : of (pair E1 E2) (cross T1 T2)
           <- of E1 T1
           <- of E2 T2.
tp_fst  : of (fst E) T1
           <- of E (cross T1 T2).
tp_snd  : of (snd E) T2
           <- of E (cross T1 T2).
% Functions
tp_lam : of (lam E) (arrow T1 T2)
          <- ({x:exp} of x T1 -> of (E x) T2).
tp_app : of (app E1 E2) T1
          <- of E1 (arrow T2 T1)
          <- of E2 T2.
% Definitions
tp_letv : of (letv E1 E2) T2
           <- of E1 T1
           <- ({x:exp} of x T1 -> of (E2 x) T2).
tp_letn : of (letn E1 E2) T2
           <- of E1 T1
           <- of (E2 E1) T2.
% Recursion
tp_fix : of (fix E) T
          <- ({x:exp} of x T -> of (E x) T).

As for evaluation, we take advantage of compositionality in order to represent substitution of an expression for a bound variable in representation of tp letn,

Δ ▷ e_{1} : τ_{1} Δ ▷ [e_{1}/x]e_{2} : τ_{2}
tp letn.
Δ ▷ let name x = e_{1} in e_{2} : τ_{2}

Since we are using higher-order abstract syntax, e_{2} is represented together with its bound variable as a function of type exp → exp. Applying this function to the representation of e_{1} yields the representation of [e_{1}/x]e_{2}.

The Elf declarations above are suggestive of an operational interpretation as a program for type inference. The idea is to pose queries of the form ?- of ⌜e⌝ T. where T is a free variable subject to instantiation and e is a concrete Mini-ML expression. We begin by considering a simple example: lam x. ⟨x, s x⟩. For this purpose we assume that of has been declared dynamic and exp and tp are static. This means that free variables of type exp and tp may appear in an answer.

?- of (lam [x] pair x (s x)) T.
Resolved with clause tp_lam
?- {x:exp} of x T1 -> of (pair x (s x)) T2.

In order to perform this first resolution step, the interpreter performed the substitutions

E = [x:exp] pair x (s x),
T1 = T1,
T2 = T2,
T = arrow T1 T2.

where E, T1, and T2 come from the clause tp_lam, and T appears in the original query. Now the interpreter applies the rules for solving goals of functional type and introduces a new parameter !x.

Introducing new parameter !x : exp
!x : exp
?- of !x T1 -> of (pair !x (s !x)) T2.
Introducing new parameter !P : of !x T1.
!x : exp,
!P : of !x T1
?- of (pair !x (s !x)) T2.
Resolved with clause tp_pair

This last resolution again requires some instantiation. We have

E1 = !x,
E2 = (s !x),
T2 = cross T21 T22.

Here, E1, E2, T21, and T22 come from the clause (the latter two renamed from T1 and T2, respectively). Now we have to solve two subgoals, namely ?- of !x T21. and ?- of (s !x) T22. The first subgoal immediately succeeds by using the assumption !P, which requires the instantiation T21 = T1 (or vice versa).

!x : exp,
!P : of !x T1
?- of !x T21.
Resolved with clause !P

Here is the remainder of the computation.

!x : exp,
!P : of !x T1
?- of (s !x) T22.
Resolved with clause tp_s

This instantiates T22 to nat and produces one subgoal.

!x : exp,
!P : of !x T1
?- of !x nat.
Resolved with clause !P

This last step instantiates T1 (and thereby indirectly T21) to nat. Thus we obtain the final answer

T = arrow nat (cross nat nat).

We can also ask for the typing derivation Q:

?- Q : of (lam [x] pair x (s x)) T.
T = arrow nat (cross nat nat),
Q = tp_lam [x:exp] [P:of x nat] tp_pair (tp_s P) P.

There will always be at most one answer to a type query, since for each expression constructor there exists at most one applicable clause. Of course, type inference will fail for ill-typed queries, and it will report failure, again because the rules are syntax-directed. We have stated above that there will be at most one answer yet we also know that types of expressions such as lam x. x are not unique. This apparent contradiction is resolved by noting that the given answer subsumes all others in the sense that all other types will be instances of the given type. This deep property of Mini-ML type inference is called the principal type property.

?- of (lam [x] x) T.
Resolved with clause tp_lam
?- {x:exp} of x T1 -> of x T2.
Introducing new parameter !x
?- of !x T1 -> of !x T2.
Assuming !P1 : of !x T1
?- of !x T2.
Resolved with clause !P1 [with T2 = T1]
T = arrow T1 T1.

Here the final answer contains a free variable T1 of type tp. This is legal, since we have declare tp to be a static type. Any instance of the final answer will yield an answer to the original problem and an object of the requested type. This can be expressed by stating that search has constructed a closed object, namely

  ([T1:tp] tp_lam ([x:exp] [P:of x T1] P)) :
  {T1:tp} of (lam ([x:exp] x)) (arrow T1 T1).

If we interpret this result as a deduction, we see that search has constructed a deduction of a parametric judgment, namely that ▷ lam x. x : τ_{1} → τ_{1} for any concrete type τ_{1}. In order to include such generic derivations we permitted type variables α in our language. The most general or principal derivation above would then be written (in two different notations):

\frac{u}{▷ x : α}

tp lam^{x,u}
▷ lam x. x : α → α

\frac{tp var}{x:α ▷ x : α}

tp lam

▷ lam x. x : α → α

From the program above one can see, that the type inference problem has been reduced to the satisfiability of some equations which arise from the question if a clause head and the goal have a common instance. For example, the goal ?- of (lam [x] x) (cross T1 T2). will fail immediately, since the only possible rule, tp_lam, is not applicable because arrow T1 T2 and cross T1 T2 do not have a common instance. The algorithm for finding common instances which also has the additional property that it does not make any unnecessary instantiation is called a unification algorithm. For first-order terms (such as LF objects of type tp in the type inference problem) a least committed common instance can always be found and is unique (modulo renaming of variables). When variables are allowed to range over functions, this is no longer the case. For example, consider the objects E2 z and pair z z, where E2 is a free variable of type exp -> exp. Then there are four canonical closed solutions for E2:^{1}

  E2 = [x:exp] pair x x ;
  E2 = [x:exp] pair z x ;
  E2 = [x:exp] pair x z ;
  E2 = [x:exp] pair z z.

In general, the question whether two objects have a common instance in the LF type theory is undecidable. This follows from the same result (due to Goldfarb [Gol81]) for a much weaker theory, the second-order fragment of the simply-typed lambdacalculus.

The operational reading of LF we sketched so far thus faces a difficulty: one of the basic steps (finding a common instance) is an undecidable problem, and, moreover, may not have a least committed solution. We deal with this problem by approximation: Elf employs an algorithm which finds a greatest common instance or detects failure in many cases and postpones other equations which must be satisfied as constraints. In particular, it will solve all problems which are essentially first order, as they arise in the type inference program above. Thus Elf is in spirit a constraint logic programming language, even though in many aspects it goes beyond the definition of the CLP family of languages described by Jaffar and Lassez [JL87]. The algorithm, originally discovered by Miller in the simply-typed λ-calculus [Mil91] has been generalized to dependent and polymorphic types in [Pfe91c]. The precise manner in which it is employed in Elf is described in [Pfe91a].^{2}. Here we content ourselves with a simple example which illustrates how constraints may arise.

1A fifth possibility, E2 = pair z is not canonical and η-equivalent to the second solution.

  ?- of (lam [x] x) ((F:tp -> tp) nat).
  F = F.
  (( arrow T1 T1 = F nat ))

Here the remaining constraint is enclosed within double parentheses. Any solution to this equation yields an answer to the original query. It is important to realize that this constitutes only a conditional success, that is, we can in general not be sure that the given constraint set is indeed be satisfiable. In the example above, this is obvious: there are infinitely many solutions of which we show two.

  F = [T:tp] arrow T1 T1,
  T1 = T1 ;
  F = [T:tp] arrow (arrow T T) (arrow T T),
  T1 = arrow nat nat.

The same algorithm is also employed during Elf’s term reconstruction phase. In practice this means that Elf term reconstruction may also terminate with remaining constraints which, in this case, is considered an error and accompanied by a request to the programmer to supply more type information.

The operational behavior of the program above may not be satisfactory from the point of view of efficiency, since expressions bound to a variable by a let name are type-checked once for each occurrence of the variable in the body of the expression. The following is an example for a derivation involving let name in Elf.

  ?- Q : of (letn (lam [y] y) ([f] pair (app f z) (app f (pair z z)))) T.
  Solving...
  T = cross nat (cross nat nat),
  Q =
     tp_letn

2[further discussion of unification elsewhere?]

        (tp_pair
            (tp_app (tp_pair tp_z tp_z)
                (tp_lam [x:exp] [P:of x (cross nat nat)] P))
            (tp_app tp_z (tp_lam [x:exp] [P:of x nat] P)))
        (tp_lam [x:exp] [P:of x T1] P).
  ;
  no more solutions

Notice the two occurrences of tp_lam which means that (lam [y] y) was typechecked twice. Usually, ML’s type system is defined with explicit constructors for polymorphic types so that we can express ▷ lam x. x : ∀t. t → t. The type inference algorithm can then instantiate such a most general type in the body e_{2} of a let name-expression let name x = e_{1} in e_{2} without type-checking e_{1} again. This is the essence of Milner’s algorithm W in [Mil78]. It is difficult to realize this algorithm directly in Elf. Some further discussion and avenues towards a possible solution are given in [Har90], [DP91], and [Lia95]. . Theoretically, however, algorithm W of [Mil78] is not more efficient compared to the algorithm presented above as shown by [?].

5.6 Representing the Proof of Type Preservation

We now return to the proof of type preservation from Section 2.6. In order to prepare for its representation in Elf, we reformulate the theorem to explicitly mention the deductions involved.

For any e, v, τ, D :: e ↪ v, and P :: ▷ e : τ there exists a Q :: ▷ v : τ.

The proof is by induction on the structure of D and relies heavily on inversion to predict the shape of P from the structure of e. The techniques from Section 3.7 suggest casting this proof as a higher-level judgment relating D, P, and Q. This higher-level judgment can be represented in LF and then be implemented in Elf as a type family. We forego the intermediate step and directly map the informal proof into Elf, calling the type family tps.

  tps : eval E V -> of E T -> of V T -> type.

All of the cases in the induction proof now have a direct representation in Elf. The interesting cases involve appeals to the substitution lemma (Lemma 2.4).

Case: D = \frac{ev z.}{z ↪ z}

Then we have to show that for any type τ such that ▷ z : τ is derivable, ▷ z : τ is derivable. This is obvious.

There are actually two slightly different, but equivalent realizations of this case. The first uses the deduction P :: ▷ z : τ that exists by assumption.

  tps_z0 : tps (ev_z) P P.

The second, which we prefer, uses inversion to conclude that P must be tp z, since it is the only rule which assigns a type to z.

  tps_z     : tps (ev_z) (tp_z) (tp_z).

The appeal to the inversion principle is implicit in these declarations. For each D and P there should be a Q such that tps ⌜D⌝ ⌜P⌝ ⌜Q⌝ is inhabited. The declaration above appears to work only for the case where the second argument P is the axiom tp z. But by inversion we know that this is the only possible case. This pattern of reasoning is applied frequently when representing proofs of metatheorems.

The next case deals with the successor constructor for natural numbers. We have taken the liberty of giving names to the deductions whose existence is shown in the proof in Section 2.6. This will help use to connect the informal statement with its implementation in Elf.

Case: D = \begin{matrix}D_{1} \\ e_{1} ↪ v_{1} \end{matrix} ; ev s. ; s e_{1} ↪ s v_{1} Then

P :: ▷ s e_{1} : τ  By assumption
P_{1} :: ▷ e_{1} : nat and τ = nat  By inversion
Q_{1} :: ▷ v_{1} : nat  By ind. hyp. on D_{1}
Q :: ▷ s v_{1} : nat  By rule tp s

Recall that an appeal to the induction hypothesis is modelled by a recursive call in the program which implements the proof. Here, the induction hypothesis is applied to D_{1} :: e_{1} ↪ v_{1} and P_{1} :: ▷ e_{1} : nat to conclude that there is a Q_{1} :: ▷ v_{1} : nat. This is what we needed to show and can thus be directly returned.

  tps_s     : tps (ev_s D1) (tp_s P1) (tp_s Q1)
               <- tps D1 P1 Q1.

We return to the cases involving case-expressions later after we have discussed the case for functions. The rules for pairs are straightforward.

  tps_pair : tps (ev_pair D2 D1) (tp_pair P2 P1) (tp_pair Q2 Q1)
                <- tps D1 P1 Q1
                <- tps D2 P2 Q2.

  tps_fst  : tps (ev_fst D1) (tp_fst P1) Q1
                <- tps D1 P1 (tp_pair Q2 Q1).
  tps_snd  : tps (ev_snd D1) (tp_snd P1) Q2
                <- tps D1 P1 (tp_pair Q2 Q1).

There is an important phenomenon one should note here. Since we used the backwards arrow notation in the declarations for ev_pair and tp_pair

  ev_pair : eval (pair E1 E2) (pair V1 V2)
               <- eval E1 V1
               <- eval E2 V2.
  tp_pair : of (pair E1 E2) (cross T1 T2)
             <- of E1 T1
             <- of E2 T2.

their arguments are reversed from what one might expect. This is why we called the first argument to ev_pair above D2 and the second argument D1, and similiary for tp_pair. The case for lam-expressions is simple, since they evaluate to themselves. For stylistic reasons we apply inversion here as in all other cases.

  tps_lam     : tps (ev_lam) (tp_lam P) (tp_lam P).

The case for evaluating an application e_{1} e_{2} is more complicated than the cases above. The informal proof appeals to the substitution lemma.

Case: D = \begin{matrix}D_{1} \\ e_{1} ↪ lam x. e′_{1} \end{matrix}  \begin{matrix}D_{2} \\ e_{2} ↪ v_{2} \end{matrix}  \begin{matrix}D_{3} \\ [v_{2}/x]e′_{1} ↪ v \end{matrix} ev app e_{1} e_{2} ↪ v.
P :: ▷ e_{1} e_{2} : τ_{1}  By assumption
P_{1} :: ▷ e_{1} : τ_{2} → τ_{1} and P_{2} :: ▷ e_{2} : τ_{2} for some τ_{2}  By inversion
Q_{1} :: ▷ lam x. e′_{1} : τ_{2} → τ_{1}  By ind. hyp. on D_{1}
Q′_{1} :: x:τ_{2} ▷ e′_{1} : τ_{1}  By inversion
Q_{2} :: ▷ v_{2} : τ_{2}  By ind. hyp. on D_{2}
P_{3} :: ▷ [v_{2}/x]e′_{1} : τ_{1}  By the Substitution Lemma 2.4
Q_{3} :: ▷ v : τ_{1}  By ind. hyp. on D_{3}

We repeat the declarations of the ev_app and tp_lam clauses here with some variables renamed in order to simplify the correspondence to the names used above.

  ev_lam : eval (app E1 E2) V
            <- eval E1 (lam E1')
            <- eval E2 V2

            <- eval (E1' V2) V.
  tp_lam : of (lam E1') (arrow T2 T1)
            <- ({x:exp} of x T2 -> of (E1' x) T1).

The deduction Q_{1} is a deduction of a parametric and hypothetical judgment (parametric in x, hypothetical in ▷ x:τ_{2}). In Elf this is represented as a function which, when applied to V2 and an object Q2 : of V2 T2 yields an object of type

of (E1' V2) T1, that is
  Q1' : {x:exp} of x T2 -> of (E1' x) T1.

The Elf variable E1' : exp -> exp represents λx:exp. ⌜e′_{1}⌝, and V2 represents v_{2}. Thus the appeal to the substitution lemma has been transformed into a function application using Q1', that is, P3 = Q1' V2 Q2.

  tps_app : tps (ev_app D3 D2 D1) (tp_app P2 P1) Q3
             <- tps D1 P1 (tp_lam Q1')
             <- tps D2 P2 Q2
             <- tps D3 (Q1' V2 Q2) Q3.

This may seem like black magic—where did the appeal to the substitution lemma go? The answer is that it is hidden in the proof of the adequacy theorem for the representation of typing derivations (Theorem 5.9) combined with the substitution lemma for LF itself! We have thus factored the proof effort: in the proof of the adequacy theorem, we establish that the typing judgment employs parametric and hypothetical judgments (which permit weakening and substitution). The implementation above can then take this for granted and model an appeal to the substitution lemma simply by function application.

One very nice property is the conciseness of the representation of the proofs of the meta-theorems in this fashion. Each case in the induction proof is represented directly as a clause, avoiding explicit formulation and proof of many properties of substitution, variable occurrences, etc. This is due to the principles of higher-order abstract syntax, judgments-as-types, and hypothetical judgments as functions. Another important factor is the Elf type reconstruction algorithm which eliminates the need for much redundant information. In the clause above, for example, we need to refer explicitly to only one expression (the variable V2). All other constraints imposed on applications of inferences rules can be inferred in a most general way in all of these examples. To illustrate this point, here is the fully explicit form of the above declaration, as generated by Elf’s term reconstruction.

 tps_app :
   {E:exp -> exp} {V2:exp} {E1:exp} {T:tp} {D3:eval (E V2) E1}
      {T1:tp} {Q1':{E1:exp} of E1 T1 -> of (E E1) T} {Q2:of V2 T1}

      {Q3:of E1 T} {E2:exp} {D2:eval E2 V2} {P2:of E2 T1} {E3:exp}
      {D1:eval E3 (lam E)} {P1:of E3 (arrow T1 T)}
      tps (E V2) E1 T D3 (Q1' V2 Q2) Q3 -> tps E2 V2 T1 D2 P2 Q2
         -> tps E3 (lam E) (arrow T1 T) D1 P1 (tp_lam T1 E T Q1')
         -> tps (app E3 E2) E1 T (ev_app E V2 E1 E2 E3 D3 D2 D1)
                (tp_app E2 T1 E3 T P2 P1) Q3.

We skip the two cases for case-expressions and only show their implementation. The techniques we need have all been introduced.

  tps_case_z: tps (ev_case_z D2 D1) (tp_case P3 P2 P1) Q2
               <- tps D2 P2 Q2.
  tps_case_s: tps (ev_case_s D3 D1) (tp_case P3 P2 P1) Q3
               <- tps D1 P1 (tp_s Q1')
               <- tps D3 (P3 V1 Q1') Q3.

Next, we come to the cases for definitions. For let val-expressions, no new considerations arise.

Case: D = \begin{matrix}D_{1} \\ e_{1} ↪ v_{1} \end{matrix} \begin{matrix}D_{2} \\ [v_{1}/x]e_{2} ↪ v \end{matrix} ev letv. let val x = e_{1} in e_{2} ↪ v

P :: ▷ let val x = e_{1} in e_{2} : τ  By assumption
P_{1} :: ▷ e_{1} : τ_{1} and
P_{2} :: x:τ_{1} ▷ e_{2} : τ for some τ_{1}  By inversion
Q_{1} :: ▷ v_{1} : τ_{1}  By ind. hyp. on D_{1}
P′_{2} :: ▷ [v_{1}/x]e_{2} : τ  By the Substitution Lemma 2.4
Q_{2} :: ▷ v : τ  By ind. hyp. on D_{2}

  tps_letv : tps (ev_letv D2 D1) (tp_letv P2 P1) Q2
              <- tps D1 P1 Q1
              <- tps D2 (P2 V1 Q1) Q2.

let name-expressions may at first sight appear to be the most complicated case. However, the substitution at the level of expressions is dealt with via compositionality as in evaluation, so the representation of this case is actually quite simple.

Case: D = \begin{matrix}D_{2} \\ [e_{1}/x]e_{2} ↪ v \end{matrix} ev letn. let name x = e_{1} in e_{2} ↪ v

P :: ▷ let name x = e_{1} in e_{2} : τ  By assumption
P_{2} :: ▷ [e_{1}/x]e_{2} : τ  By inversion
Q_{2} :: ▷ v : τ  By ind. hyp. on D_{2}

  tps_letn : tps (ev_letn D2) (tp_letn P2 P1) Q2
              <- tps D2 P2 Q2.

The case of fixpoint follows the same general pattern as the case for application in that we need to appeal to the substitution lemma. The solution is analogous.

Case: D = \begin{matrix}D_{1} \\ [fix x. e_{1}/x]e_{1} ↪ v \end{matrix} ev fix fix x. e_{1} ↪ v.

P :: ▷ fix x. e_{1} : τ  By assumption
P_{1} :: x : τ ▷ e_{1} : τ  By inversion
P′_{1} :: ▷ [fix x. e_{1}/x]e_{1} : τ  By the Substitution Lemma 2.4
Q_{1} :: ▷ v : τ  By ind. hyp. on D_{1}

In the representation,

  P1 : {x:exp} of x T -> of (E1 x) T

and thus

  (P1 (fix E1)) : of (fix E1) T -> of (E1 (fix E1)) T

and

  (P1 (fix E1) (tp_fix P1)) : of (E1 (fix E1)) T

This is the representation of the deduction P′_{1}, since ⌜[fix x. e_{1}/x]e_{1}⌝ = [⌜fix x. e_{1}⌝/x]⌜e_{1}⌝ ≡ (λx:exp. ⌜e_{1}⌝) (fix (λx:exp. ⌜e_{1}⌝)).

  tps_fix : tps (ev_fix D1) (tp_fix P1) Q1
              <- tps D1 (P1 (fix E1) (tp_fix P1)) Q1.

Here is a simple example which illustrates the use of the tps type family as a program. First, we generate a typing derivation P for

let name f = lam x. x in let g = f f in g g.

Then we evaluate the expression (which yields lam x. x) and finally use the proof of type preservation to generate a deduction Q which shows that the result has the same type as the original expression.

  ?- sigma [P:of (letn (lam [x] x) ([f] letn (app f f) ([g] app g g))) T]
     sigma [D:eval (letn (lam [x] x) ([f] letn (app f f) ([g] app g g))) V]
     tps D P Q.
  Solving...
  Q = tp_lam [x:exp] [P:of x T1] P,
  V = lam [x:exp] x,
  T = arrow T1 T1.

Of course, this is a very indirect way to generate a typing derivation of lam x. x, but illustrates the computational content of the type family tps we defined.

5.7 Exercises

Exercise 5.1 Carry out three representative cases in the proof of Property 5.4. Where do we require the assumption that Γ must be of a certain form? Construct a counterexample which shows the falsehood of careless generalization of the theorem to admit arbitary contexts Γ.

Exercise 5.2 Carry out three representative cases in the proof of the Adequacy Theorem 5.5.

Exercise 5.3 Modify the natural semantics for Mini-ML such that only closed λexpressions have a value. How does this affect the proof of type preservation?

Exercise 5.4 Write Elf programs

1. to count the number of occurrences of bound variables in a Mini-ML expression;

2. to remove all vacuous let-bindings from a Mini-ML expression;

3. to rewrite all occurrences of expressions of the form (lam x. e_{2}) e_{1} to let x = e_{1} in e_{2}.

Exercise 5.5 For each of the following statements, prove them informally and represent the proof in Elf, or give a counterexample if the statement is false.

1. For any expressions e_{1} and e_{2}, evaluation of (lam x. e_{2}) e_{1} yields a value v if and only if evaluation of let val x = e_{1} in e_{2} yields v.

2. For any expressions e_{1} and e_{2}, evaluation of (lam x. e_{2}) e_{1} yields a value v if and only if evaluation of let name x = e_{1} in e_{2} yields v.

3. For values v_{1}, the expression (lam x. e_{2}) v_{1} has type τ if and only if the expression let val x = v_{1} in e_{2} has type τ.

4. For values v_{1}, the expression (lam x. e_{2}) v_{1} has type τ if and only if the expression let name x = v_{1} in e_{2} has type τ.

5. Evaluation is deterministic, that is, whenever e ↪ v_{1} and e ↪ v_{2} then v_{1} = v_{2} (modulo renaming of bound variables, as usual).

Exercise 5.6 Give an LF representation of the fragment of Mini-ML which includes pairing, first and second projection, functions and application, and definitions with let val without using hypothetical judgments. Thus the typing judgment should be represented as a ternary type family, say, hastype, indexed by a representation of the context Δ and representations of e and τ. We would then look for a representation function ⌜·⌝ which satisfies

Γ ⊢ ⌜P⌝ : hastype ⌜Δ⌝ ⌜e⌝ ⌜τ⌝

for a suitable Γ, whenever P is a valid deduction of Δ ▷ e : τ.

Exercise 5.7 Illustrate by means of an example why declaring the type tp as dynamic might lead to undesirable backtracking and unexpected answers during type inference for Mini-ML with the program in Section 5.5. Can you construct a situation where the program diverges on a well-typed Mini-ML expression? How about on a Mini-ML expression which is not well-typed?

Exercise 5.8 Extend the implementation of the Mini-ML interpreter, type inference, and proof of type preservation to include

1. unit, void, and disjoint sum types (see Exercise 2.6),

2. lists (see Exercise 2.7).

Exercise 5.9 Consider the call-by-name version of Mini-ML with lazy constructors as sketched in Exercise 2.12. Recall that neither the arguments to functions, nor the arguments to constructors (s and ⟨·, ·⟩) should be evaluated.

1. Implement an interpreter for the language and show a few expressions that highlight the differences in the operational semantics.

2. Implement type inference.

3. Define and implement a suitable notion of value.

4. Prove value soundness and implement your proof.

5. Prove type preservation and implement your proof.

Discuss the main differences between the development for Mini-ML and its call-by-name variant.

Exercise 5.10 The definition of the judgment e Closed follows systematically from the representation of expressions in higher-order abstract syntax, because objectlevel variables are represented by meta-level variables. This exercise explores a generalization of this fact. Assume we have a signature Σ_{0} in the simply-typed lambda-calculus that declares exactly one type constant a and some unspecified number of object constants c_{1}, …, c_{n}. Define an LF signature Σ_{1} that extends Σ_{0} by a new family

closed : a → type

such that

⊢_{Σ_{0}} N : a

if and only if

Γ ⊢_{Σ_{1}} N : a and Γ ⊢_{Σ_{1}} M : closed N

provided Γ no has declaration of the form u:Πy_{1}:A_{1} … Πy_{m}:A_{m}. closed P.

Exercise 5.11 Write Elf programs to determine if a Mini-ML expression is free of the recursion operation fix and at the same time

1. linear (every bound variable occurs exactly once);

2. affine (every bound variable occurs at most once);

3. relevant (every bound variable occurs at least once).

Since only one branch in a case statement will be taken during evaluation, a bound variable must occur exactly once in each branch in a linear expression, may occur at most once in each branch in an affine expression, and must occur at least once in each branch in a relevant expression.

Exercise 5.12 Instead of substituting in the typing rule for let name-expressions we could extend contexts to record the definitions for variables bound with let name.

Contexts Δ ::= · | Δ, x:τ | Δ, x = e

Variables must still occur at most once in a context (no variable may be declared and defined). We would replace the rule tp letn by the following two rules.

Δ ▷ e_{1} : τ_{1} Δ, x = e_{1} ▷ e_{2} : τ_{1} tp letn0

Δ ▷ let name x = e_{1} in e_{2} : τ_{2}

x = e in Δ Δ ▷ e : τ

tp var0

Δ ▷ x : τ

There are at least two ways we can view this modification for representation in the framework.

1. We use a new judgment, x = e, which is introduced only as a hypothesis into a derivation.

2. We view a hypothesis x = e as the assumption of an inference rule. We might write this as

▷ e_{1} : τ_{1}  \begin{aligned}▷ e_{1} : τ \\ & u_{τ} \\ ▷ x : τ \end{aligned} ; ⋮ ; ▷ e_{2} : τ_{2}
tp let^{x,u}.
▷ let name x = e_{1} in e_{2}

The subscript τ in the hypothetical rule u indicates that in each application of u we may choose a different type τ. Hypothetical rules have been investigated by Schroeder-Heister [SH84].

1. Show the proper generalization and the new cases in the informal proof of type preservation using rules tp letn0 and tp var0.

2. Give the Elf implementation of type inference using alternative 1.

3. Implement the modified proof of type preservation in Elf using alternative 1.

4. Give the Elf implementation of type inference using alternative 2.

5. Implement the modified proof of type preservation in Elf using alternative 2.

Exercise 5.13 [ on the value restriction or its absence ]

Exercise 5.14 [ on interpreting let name as let value, connect to value restriction ]