Library STLCsol


The simply-typed lambda calculus in Coq.

An interactive tutorial on developing programming language metatheory. This file uses the simply-typed lambda calculus (STLC) to demonstrate the locally nameless representation of lambda terms and cofinite quantification in judgments.

This tutorial concentrates on "how" to formalize STLC; for more details about "why" we use this style of development see: "Engineering Formal Metatheory", Aydemir, Chargu'eraud, Pierce, Pollack, Weirich. POPL 2008.

Tutorial authors: Brian Aydemir and Stephanie Weirich, with help from Aaron Bohannon, Nate Foster, Benjamin Pierce, Jeffrey Vaughan, Dimitrios Vytiniotis, and Steve Zdancewic. Adapted from code by Arthur Chargu'eraud.

Draft of 3/21/2009

Contents

  • Syntax of STLC
  • Substitution
  • Free variables
  • Open
  • Local closure
  • Properties about basic operations
  • Cofinite quantification
  • Tactic support
  • Typing environments
  • Typing relation
  • Weakening
  • Substitution
  • Values and evaluation
  • Preservation
  • Progress
  • Additional properties
  • Renaming
  • Decidability of typechecking
  • Equivalence of exist fresh and cofinite


Solutions to exercises are in STLCsol.v.

First, we import a number of definitions from the Metatheory library (see Metatheory.v). The following command makes those definitions available in the rest of this file. This command will only succeed if you have already run "make" in the tutorial directory to compile the Metatheory library.

Require Import Metatheory.

Syntax of STLC


We use a locally nameless representation for the simply-typed lambda calculus, where bound variables are represented as natural numbers (de Bruijn indices) and free variables are represented as atoms.

The type atom, defined in the Atom library, represents names. Equality on names is decidable (eq_atom_dec), and it is possible to generate an atom fresh for any given finite set of atoms (atom_fresh_for_set, in the AtomSet library).

Inductive typ : Set :=
  | typ_base : typ
  | typ_arrow : typtyptyp.

Inductive exp : Set :=
  | bvar : natexp
  | fvar : atomexp
  | abs : typexpexp
  | app : expexpexp.

Coercion bvar : nat >-> exp.
Coercion fvar : atom >-> exp.

We declare the constructors for indices and variables to be coercions. That way, if Coq sees a nat where it expects an exp, it will implicitly insert an application of bvar; and similarly for atoms.

For example, we can encode the expression (\x:b. Y x) as below. Because "Y" is free variable in this term, we need to assume an atom for this name.

Parameter Y : atom.
Definition demo_rep1 := abs typ_base (app Y 0).

Note that because of the coercions we may write abs (app Y 0) instead of abs (app (fvar Y) (bvar 0)).

Below is another example: the encoding of (\x:b. \y:b. (y x)).

Definition demo_rep2 := abs typ_base (abs typ_base (app 0 1)).

Exercise: Uncomment and then complete the definitions of the following lambda calculus terms using the locally nameless representation.

"two" : \s:b->b. \z:b. s (s z)

"COMB_K" : \x:b. \y:b. x

"COMB_S" : \x:b -> b -> b.\y:b -> b.\z:b. x z (y z)


Definition two :=
  
  abs (typ_arrow typ_base typ_base)
                      (abs typ_base (app 1 (app 1 0))).

Definition COMB_K :=
  
        abs typ_base
    (abs typ_base 1).

Definition COMB_S :=
   
   abs (typ_arrow typ_base (typ_arrow typ_base typ_base))
    (abs (typ_arrow typ_base typ_base)
      (abs (typ_base)
        (app (app 2 0) (app 1 0)))).

There are two important advantages of the locally nameless representation:
  • Alpha-equivalent terms have a unique representation. We're always working up to alpha-equivalence.
  • Operations such as free variable substitution and free variable calculation have simple recursive definitions (and therefore are simple to reason about).


Weighed against these advantages are two drawbacks:
  • The exp datatype admits terms, such as abs 3, where indices are unbound. A term is called "locally closed" when it contains no unbound indices.
  • We must define *both* bound variable & free variable substitution and reason about how these operations interact with each other.

Substitution


Substitution replaces a free variable with a term. The definition below is simple for two reasons:
  • Because bound variables are represented using indices, there is no need to worry about variable capture.
  • We assume that the term being substituted in is locally closed. Thus, there is no need to shift indices when passing under a binder.

Fixpoint subst (z : atom) (u : exp) (e : exp)
  {struct e} : exp :=
  match e with
    | bvar ibvar i
    | fvar xif x == z then u else (fvar x)
    | abs t e1abs t (subst z u e1)
    | app e1 e2app (subst z u e1) (subst z u e2)
  end.

The Fixpoint keyword defines a Coq function. As all functions in Coq must be total. The annotation {struct e} indicates the termination metric---all recursive calls in this definition are made to arguments that are structurally smaller than e.

Note also that subst uses the notation x == z for decidable atom equality. This notation is defined in the Metatheory library.

We define a notation for free variable substitution that mimics standard mathematical notation.

Notation "[ z ~> u ] e" := (subst z u e) (at level 68).

To demonstrate how free variable substitution works, we need to reason about atom equality.

Parameter Z : atom.
Check (Y == Z).

The decidable atom equality function returns a sum. If the two atoms are equal, the left branch of the sum is returned, carrying a proof of the proposition that the atoms are equal. If they are not equal, the right branch includes a proof of the disequality.

The demo below uses three new tactics:
  • The tactic simpl reduces a Coq expression to its normal form.
  • The tactic destruct (Y==Y) considers the two possible results of the equality test.
  • The tactic Case marks cases in the proof script. It takes any string as its argument, and puts that string in the hypothesis list until the case is finished.

Lemma demo_subst1:
  [Y ~> Z] (abs typ_base (app 0 Y)) = (abs typ_base (app 0 Z)).


Take-home Exercise: We can use almost the same proof script as above to state how substitution works in the variable case. Try it on your own.

Lemma subst_eq_var: ∀ (x : atom) u,
  [x ~> u]x = u.


Lemma subst_neq_var : ∀ (x y : atom) u,
  yx → [x ~> u]y = y.


Free variables


The function fv, defined below, calculates the set of free variables in an expression. Because we are using a locally nameless representation, where bound variables are represented as indices, any name we see is a free variable of a term. In particular, this makes the abs case simple.

Fixpoint fv (e : exp) {struct e} : atoms :=
  match e with
    | bvar iempty
    | fvar xsingleton x
    | abs t e1fv e1
    | app e1 e2 ⇒ (fv e1) `union` (fv e2)
  end.

The type atoms represents a finite set of elements of type atom. The notation for infix union is defined in the Metatheory library.

Lemma f_equal_demo : ∀ e1 e2, e1 = e2fv e1 = fv e2.


Lemma fsetdec_demo : ∀ (x :atom) (S : atoms),
  x `in` (singleton x `union` S).

Exercise subst_fresh To show the ease of reasoning with these definitions, we will prove a standard result from lambda calculus: if a variable does not appear free in a term, then substituting for it has no effect.

HINTS: Prove this lemma by induction on e.

  • You will need to use simpl in many cases. You can simpl everything everywhere (including hypotheses) with the pattern simpl in *.


  • Part of this proof includes a false assumption about free variables. Destructing this hypothesis produces a goal about finite set membership that is solvable by fsetdec.

Lemma subst_fresh : ∀ (x : atom) e u,
  x `notin` fv e → [x ~> u] e = e.


Lemma subst_notin_fv : ∀ x y u e,
   x `notin` fv ex `notin` fv u
   x `notin` fv ([y ~> u]e).



Opening


Opening replaces an index with a term. It corresponds to informal substitution for a bound variable, such as in the rule for beta reduction. Note that only "dangling" indices (those that do not refer to any abstraction) can be opened. Opening has no effect for terms that are locally closed.

Natural numbers are just an inductive datatype with two constructors: O (as in the letter 'oh', not 'zero') and S, defined in Coq.Init.Datatypes. Coq allows literal natural numbers to be written using standard decimal notation, e.g., 0, 1, 2, etc. The notation k === i is the decidable equality function for natural numbers (cf. Coq.Peano_dec.eq_nat_dec). This notation is defined in the Metatheory library.

We make several simplifying assumptions in defining open_rec.

First, we assume that the argument u is locally closed. This assumption simplifies the implementation since we do not need to shift indices in u when passing under a binder. Second, we assume that this function is initially called with index zero and that zero is the only unbound index in the term. This eliminates the need to possibly subtract one in the case of indices.

There is no need to worry about variable capture because bound variables are indices.

Fixpoint open_rec (k : nat) (u : exp)(e : exp)
  {struct e} : exp :=
  match e with
    | bvar iif k === i then u else (bvar i)
    | fvar xfvar x
    | abs t e1abs t (open_rec (S k) u e1)
    | app e1 e2app (open_rec k u e1) (open_rec k u e2)
  end.

Many common applications of opening replace index zero with an expression or variable. The following definition provides a convenient shorthand for such uses. Note that the order of arguments is switched relative to the definition above. For example, (open e x) can be read as "substitute the variable x for index 0 in e" and "open e with the variable x." Recall that the coercions above let us write x in place of (fvar x).

Definition open e u := open_rec 0 u e.

This next demo shows the operation of open. For example, the locally nameless representation of the term (\y. (\x. (y x)) y) is abs (app (abs (app 1 0)) 0). To look at the body without the outer abstraction, we need to replace the indices that refer to that abstraction with a name. Therefore, we show that we can open the body of the abs above with Y to produce app (abs (app Y 0)) Y).

Lemma demo_open :
  open (app (abs typ_base (app 1 0)) 0) Y =
       (app (abs typ_base (app Y 0)) Y).



Local closure


Recall that exp admits terms that contain unbound indices. We say that a term is locally closed when no indices appearing in it are unbound. The proposition lc e holds when an expression e is locally closed.

The inductive definition below formalizes local closure such that the resulting induction principle serves as the structural induction principle over (locally closed) expressions. In particular, unlike induction for type exp, there are no cases for bound variables. Thus, the induction principle corresponds more closely to informal practice than the one arising from the definition of pre-terms.

Inductive lc : expProp :=
  | lc_var : ∀ (x:atom),
      lc x
  | lc_abs : ∀ (x : atom) e t,
      x `notin` fv elc (open e x) →
      lc (abs t e)
  | lc_app : ∀ e1 e2,
      lc e1lc e2
      lc (app e1 e2).

Hint Constructors lc.

Properties about basic operations

We also define a notation for open_rec to make stating some of the properties simpler. However, we don't need to use open_rec outside of this part of the tutorial so we make it a local notation, confined to this section.

Section BasicOperations.
Notation Local "{ k ~> u } t" := (open_rec k u t) (at level 67).

The first property we would like to show is the analogue to subst_fresh: index substitution has no effect for closed terms.

Here is an initial attempt at the proof.

Lemma open_rec_lc_0 : ∀ k u e,
  lc e
  e = {k ~> u} e.

At this point there are two problems. Our goal is about substitution for index S k in term e, while our induction hypothesis IHLC only tells use about index k in term open e x.

To solve the first problem, we generalize our IH over all k. That way, when k is incremented in the abs case, it will still apply. Below, we use the tactic generalize dependent to generalize over k before using induction.

Lemma open_rec_lc_1 : ∀ k u e,
  lc e
  e = {k ~> u} e.


At this point we are still stuck because the IH concerns open e x instead of e. The result that we need is that if an index substitution has no effect for an opened term, then it has no effect for the raw term (as long as we are *not* substituting for 0, hence S k below).
   open e x = {S k ~> u}(open e x)  -> e = {S k ~> u} e


In other words, expanding the definition of open:
   {0 ~> x}e = {S k ~> u}({0 ~> x} e)  -> e = {S k ~> u} e


Of course, to prove this result, we must generalize 0 and S k to be any pair of inequal numbers to get a strong enough induction hypothesis for the abs case.

Lemma open_rec_lc_core : ∀ e j v i u,
  ij
  {j ~> v} e = {i ~> u} ({j ~> v} e) →
  e = {i ~> u} e.





Lemma open_rec_lc_core_automated : ∀ e j v i u,
  ij
  {j ~> v} e = {i ~> u} ({j ~> v} e) →
  e = {i ~> u} e.

With the help of this lemma, we can complete the proof.

Lemma open_rec_lc : ∀ k u e,
  lc ee = {k ~> u} e.

Take-home Exercise subst_open_rec

The next lemma demonstrates that free variable substitution distributes over index substitution.

The proof of this lemma is by straightforward induction over e1. When e1 is a free variable, we need to appeal to open_rec_lc, proved above.

Lemma subst_open_rec : ∀ e1 e2 u (x : atom) k,
  lc u
  [x ~> u] ({k ~> e2} e1) = {k ~> [x ~> u] e2} ([x ~> u] e1).

Exercise subst_open_var


The lemma above is most often used with k = 0 and e2 as some fresh variable. Therefore, it simplifies matters to define the following useful corollary.

HINT: Do not use induction. Rewrite with subst_open_rec and subst_neq_var.


Lemma subst_open_var : ∀ (x y : atom) u e,
  yx
  lc u
  open ([x ~> u] e) y = [x ~> u] (open e y).

Take-home Exercise subst_intro


This lemma states that opening can be replaced with variable opening and substitution.

HINT: Prove by induction on e, first generalizing the argument to open_rec by using the generalize tactic, e.g., generalize 0.


Lemma subst_intro : ∀ (x : atom) u e,
  x `notin` (fv e) →
  open e u = [x ~> u](open e x).







End BasicOperations.

Cofinite quantification


Lemma subst_lc_0 : ∀ (x : atom) u e,
  lc e
  lc u
  lc ([x ~> u] e).
    Print lc_abs.
Admitted.

Here we are stuck. We don't know that x0 is not in the free variables of u.

The solution is to change the *definition* of local closure so that we get a different induction principle. Currently, in the lc_abs case, we show that an abstraction is locally closed by showing that the body is locally closed after it has been opened with one particular variable.

  | lc_abs : forall (x : atom) e,
      x `notin` fv e ->
      lc (open e x) ->
      lc (abs e)


Therefore, our induction hypothesis in this case only applies to that variable. From the hypothesis list in the lc_abs case:

x0 : atom, IHHe : lc (x ~> uopen e x0)

The problem is that we don't have any assumptions about x0. It could very well be equal to x.

A stronger induction principle provides an IH that applies to many variables. In that case, we could pick one that is "fresh enough". To do so, we need to revise the above definition of lc and replace the type of lc_abs with this one:

  | lc_abs_c : forall L e,
      (forall x:atom, x `notin` L -> lc (open e x)) ->
      lc (abs e)


This rule says that to show that an abstraction is locally closed, we need to show that the body is closed, after it has been opened by any atom x, *except* those in some set L. With this rule, the IH in this proof is now:

H0 : forall x0 : atom, x0 `notin` L -> lc (x ~> uopen e x0)

Below, lc_c is the local closure judgment revised to use this new rule in the abs case. We call this "cofinite quantification" because the IH applies to an infinite number of atoms x0, except those in some finite set L.

Changing the rule in this way does not change what terms are locally closed. (For more details about cofinite-quantification see: "Engineering Formal Metatheory", Aydemir, Chargu'eraud, Pierce, Pollack, Weirich. POPL 2008.)


Inductive lc_c : expProp :=
  | lc_var_c : ∀ (x:atom),
      lc_c x
  | lc_abs_c : ∀ (L : atoms) e T,
      (∀ x : atom, x `notin` Llc_c (open e x)) →
      lc_c (abs T e)
  | lc_app_c : ∀ e1 e2,
      lc_c e1
      lc_c e2
      lc_c (app e1 e2).

Hint Constructors lc_c.


Section CofiniteQuantification.
Notation Local "{ k ~> u } t" := (open_rec k u t) (at level 67).


Lemma open_rec_lc_c : ∀ k u e,
  lc_c e
  e = {k ~> u} e.



Lemma subst_open_rec_c : ∀ e1 e2 u (x : atom) k,
  lc_c u
  [x ~> u] ({k ~> e2} e1) = {k ~> [x ~> u] e2} ([x ~> u] e1).

Lemma subst_open_var_c : ∀ (x y : atom) u e,
  yx
  lc_c u
  open ([x ~> u] e) y = [x ~> u] (open e y).


Lemma subst_lc_c : ∀ (x : atom) u e,
  lc_c e
  lc_c u
  lc_c ([x ~> u] e).



End CofiniteQuantification.

Tactic support


When picking a fresh atom or applying a rule that uses cofinite quantification, choosing a set of atoms to be fresh for can be tedious. In practice, it is simpler to use a tactic to choose the set to be as large as possible.

The first tactic we define, gather_atoms, is used to collect together all the atoms in the context. It relies on an auxiliary tactic, ltac_map (from the AdditionalTactics, which is imported as part of the Metatheory library), which maps a function over all hypotheses wit the appropriate type. In this case, the argument to each invocation of ltac_map is a function that should return the set of atoms appearing in its argument.

Ltac gather_atoms :=
  let A := ltac_map (fun x : atomsx) in
  let B := ltac_map (fun x : atomsingleton x) in
  let C := ltac_map (fun x : list (atom * typ) ⇒ dom x) in
  let D := ltac_map (fun x : expfv x) in
  simplify_list_of_atom_sets (A ++ B ++ C ++ D).

We can use gather_atoms to define a variant of the (pick fresh x for L) tactic, which we call (pick fresh x). The tactic chooses an atom fresh for "everything" in the context.

Tactic Notation "pick" "fresh" ident(x) :=
  let L := gather_atoms in
  (pick fresh x for L).

We can also use gather_atoms to define a tactic for applying a rule that is defined using cofinite quantification. The tactic (pick fresh x and apply H) applies a rule H, just as the apply tactic would. However, the tactic also picks a sufficiently fresh name x to use.

Note: We define this tactic in terms of another tactic, (pick fresh x excluding L and apply H), which is defined and documented in the Metatheory library.

Tactic Notation
      "pick" "fresh" ident(atom_name) "and" "apply" constr(lemma) :=
  let L := gather_atoms in
  pick fresh atom_name excluding L and apply lemma.

Example



Below, we reprove subst_lc_c using (pick fresh and apply). Step through the proof below to see how (pick fresh and apply) works.

Lemma subst_lc_c_alternate_proof : ∀ (x : atom) u e,
  lc_c e
  lc_c u
  lc_c ([x ~> u] e).




Typing environments


We represent environments as association lists (lists of pairs of keys and values) whose keys are atoms.

Notation env := (list (atom * typ)).


Environment equality

When reasoning about environments, we often need to talk about bindings in the "middle" of an environment. Therefore, it is common for lemmas and definitions to use list append in their statements. Unfortunately, list append is associative, so two Coq expressions may denote the same environment even though they are not equal.

The tactic simpl_env reassociates all concatenations of environments to the right.

Lemma append_assoc_demo : ∀ (E0 E1 E2 E3:env),
  E0 ++ (E1 ++ E2) ++ E3 = E0 ++ E1 ++ E2 ++ E3.



Notation "x ~ T" := (one (x, T)) (at level 68).


Lemma simpl_env_demo : ∀ (x y:atom) (T1 T2:typ) (E F:env),
   ((x ~ T1) ++ nil) ++ (y,T2) :: (nil ++ E) ++ F =
   (x ~ T1) ++ (y ~ T2) ++ E ++ F.


Lemma rewrite_env_demo : ∀ (x y:atom) (T:typ) P, (∀ E, P ((x,T):: E) → True) →
  P (x ~ T) → True.

Environment operations.

The ternary predicate binds holds when a given binding is present somewhere in an environment.

Lemma binds_demo : ∀ (x:atom) (T:typ) (E F:env),
  binds x T (E ++ (x ~ T) ++ F).

The function dom computes the domain of an environment, returning a finite set of atoms. Note that we cannot use Coq's equality for finite sets, we must instead use a defined relation = for atom set equality.

Lemma dom_demo : ∀ (x y : atom) (T : typ),
  dom (x ~ T) [=] singleton x.

The unary predicate uniq holds when each atom is bound at most once in an environment.

Lemma uniq_demo : ∀ (x y : atom) (T : typ),
  xyuniq ((x ~ T) ++ (y ~ T)).

Typing relation


The definition of the typing relation is straightforward. In order to ensure that the relation holds for only well-formed environments, we check in the typing_var case that the environment is uniq. The structure of typing derivations implicitly ensures that the relation holds only for locally closed expressions. Finally, note the use of cofinite quantification in the typing_abs case.

Inductive typing_c : envexptypProp :=
  | typing_var_c : ∀ E (x : atom) T,
      uniq E
      binds x T E
      typing_c E (fvar x) T
  | typing_abs_c : ∀ (L : atoms) E e T1 T2,
      (∀ (x:atom), x `notin` L
        typing_c ((x ~ T1) ++ E) (open e x) T2) →
      typing_c E (abs T1 e) (typ_arrow T1 T2)
  | typing_app_c : ∀ E e1 e2 T1 T2,
      typing_c E e1 (typ_arrow T1 T2) →
      typing_c E e2 T1
      typing_c E (app e1 e2) T2.

We add the constructors of the typing relation as hints to be used by the auto and eauto tactics.

Hint Constructors typing_c.

Weakening


Weakening states that if an expression is typeable in some environment, then it is typeable in any well-formed extension of that environment. This property is needed to prove the substitution lemma.

As stated below, this lemma is not directly proveable. The natural way to try proving this lemma proceeds by induction on the typing derivation for e.

Lemma typing_weakening_0 : ∀ E F e T,
  typing_c E e T
  uniq (F ++ E) →
  typing_c (F ++ E) e T.

We are stuck in the typing_abs_c case because the induction hypothesis H0 applies only when we weaken the environment at its head. In this case, however, we need to weaken the environment in the middle; compare the conclusion at the point where we're stuck to the hypothesis H, which comes from the given typing derivation.

We can obtain a more useful induction hypothesis by changing the statement to insert new bindings into the middle of the environment, instead of at the head. However, the proof still gets stuck, as can be seen by examining each of the cases in the proof below.

Note: To view subgoal n in a proof, use the command "Show n". To work on subgoal n instead of the first one, use the command "Focus n".

Lemma typing_weakening_strengthened_0 : ∀ (E F G : env) e T,
  typing_c (G ++ E) e T
  uniq (G ++ F ++ E) →
  typing_c (G ++ F ++ E) e T.

The hypotheses in the typing_var_c case include an environment E0 that that has no relation to what we need to prove. The missing fact we need is that E0 = (G ++ E).

The problem here arises from the fact that Coq's induction tactic let's us only prove something about all typing derivations. While it's clear to us that weakening applies to all typing derivations, it's not clear to Coq, because the environment is written using concatenation. The induction tactic expects that all arguments to a judgement are variables. So we see E0 in the proof instead of (G ++ E).

The solution is to restate the lemma. For example, we can prove

  forall E F E' e T, typing_c E' e T ->
  forall G, E' = G ++ E -> uniq (G ++ F ++ E) -> typing_c (G ++ F ++ E) e T.


The equality gets around the problem with Coq's induction tactic. The placement of the (∀ G) quantifier gives us a sufficiently strong induction hypothesis in the typing_abs_c case.

However, we prefer not to state the lemma in the way shown above, since it is not as readable as the original statement. Instead, we use a tactic to introduce the equality within the proof itself. The tactic (remember t as t') replaces an object t with the identifier t' everywhere in the goal and introduces an equality t' = t into the context. It is often combined with generalize dependent, as illustrated below.

Exercise



See how we use remember as in the proof below for weakening. Then, complete the proof below.

HINTS:

  • The typing_var_c case follows from binds_weaken, the weakening lemma for the binds relation.


  • The typing_abs_c case follows from the induction hypothesis, but the apply tactic may be unable to unify things as you might expect.


    • Recall the pick fresh and apply tactic.


    • In order to apply the induction hypothesis, use rewrite_env to reassociate the list operations.


    • After applying the induction hypothesis, use simpl_env to use uniq_push.


    • Here, use auto to solve facts about finite sets of atoms, since it will simplify the dom function behind the scenes. fsetdec does not work with the dom function.


  • The typing_app_c case follows directly from the induction hypotheses.

Lemma typing_c_weakening_strengthened : ∀ (E F G : env) e T,
  typing_c (G ++ E) e T
  uniq (G ++ F ++ E) →
  typing_c (G ++ F ++ E) e T.






Example



We can now prove our original statement of weakening. The only interesting step is the use of the rewrite_env tactic.

Lemma typing_c_weakening : ∀ (E F : env) e T,
    typing_c E e T
    uniq (F ++ E) →
    typing_c (F ++ E) e T.

Substitution


Having proved weakening, we can now prove the usual substitution lemma, which we state both in the form we need and in the strengthened form needed to make the proof go through.

  typing_c_subst_simple : forall E e u S T z,
    typing_c ((z ~ S) ++ E) e T ->
    typing_c E u S ->
    typing_c E ([z ~> u] e) T

  typing_c_subst : forall E F e u S T z,
    typing_c (F ++ (z ~ S) ++ E) e T ->
    typing_c E u S ->
    typing_c (F ++ E) ([z ~> u] e) T


The proof of the strengthened statement proceeds by induction on the given typing derivation for e. The most involved case is the one for variables; the others follow from the induction hypotheses.

Exercise



Below, we state what needs to be proved in the typing_var_c case of the substitution lemma. Fill in the proof.

Proof sketch: The proof proceeds by a case analysis on (x == z), i.e., whether the two variables are the same or not.

  • If (x = z), then we need to show (typing (F ++ E) u T). This follows from the given typing derivation for u by weakening and the fact that T must equal S.


  • If (xz), then we need to show (typing (F ++ E) x T). This follows by the typing rule for variables.


HINTS: Lemmas binds_mid_eq, uniq_remove_mid, and binds_remove_mid are useful.


Lemma typing_subst_var_case : ∀ (E F : env) u S T (z x : atom),
  binds x T (F ++ (z ~ S) ++ E) →
  uniq (F ++ (z ~ S) ++ E) →
  typing_c E u S
  typing_c (F ++ E) ([z ~> u] x) T.





Note



The other two cases of the proof of the substitution lemma are relatively straightforward. However, the case for typing_abs_c needs the fact that the typing relation holds only for locally-closed expressions.

Lemma typing_c_to_lc_c : ∀ E e T,
  typing_c E e Tlc_c e.


Exercise



Complete the proof of the substitution lemma. The proof proceeds by induction on the typing derivation for e. The initial steps should use remember as and generalize dependent in a manner similar to the proof of weakening.

HINTS:
  • Use the lemma proved above for the typing_var_c case.


  • The typing_abs_c case follows from the induction hypothesis.
    • Use simpl to simplify the substitution.


    • Recall the tactic pick fresh and apply.


    • In order to use the induction hypothesis, use subst_open_var_c to push the substitution under the opening operation.


    • Recall the lemma typing_c_to_lc_c and the rewrite_env and simpl_env tactics.


  • The typing_app_c case follows from the induction hypotheses. Use simpl to simplify the substitution.

Lemma typing_c_subst : ∀ (E F : env) e u S T (z : atom),
  typing_c (F ++ (z ~ S) ++ E) e T
  typing_c E u S
  typing_c (F ++ E) ([z ~> u] e) T.







Exercise



Complete the proof of the substitution lemma stated in the form we need it. The proof is similar to that of typing_weakening.

HINT: You'll need to use rewrite_env to prepend nil, and simpl_env to simplify nil away.

Lemma typing_c_subst_simple : ∀ (E : env) e u S T (z : atom),
  typing_c ((z ~ S) ++ E) e T
  typing_c E u S
  typing_c E ([z ~> u] e) T.


Values and Evaluation


In order to state the preservation lemma, we first need to define values and the small-step evaluation relation. These inductive relations are straightforward to define.

Note the hypotheses which ensure that the relations hold only for locally closed terms. Below, we prove that this is actually the case, since it is not completely obvious from the definitions alone.

Inductive value_c : expProp :=
  | value_abs_c : ∀ e T1,
      lc_c (abs T1 e) →
      value_c (abs T1 e).

Inductive eval_c : expexpProp :=
  | eval_beta_c : ∀ e1 e2 T1,
      lc_c (abs T1 e1) →
      value_c e2
      eval_c (app (abs T1 e1) e2) (open e1 e2)
  | eval_app_1_c : ∀ e1 e1' e2,
      lc_c e2
      eval_c e1 e1'
      eval_c (app e1 e2) (app e1' e2)
  | eval_app_2_c : ∀ e1 e2 e2',
      value_c e1
      eval_c e2 e2'
      eval_c (app e1 e2) (app e1 e2').

We add the constructors for these two relations as hints to be used by Coq's auto and eauto tactics.

Hint Constructors value_c eval_c.

Preservation


Take-home Exercise



Complete the proof of preservation. In this proof, we proceed by induction on the given typing derivation. The induction hypothesis has already been appropriately generalized by the given proof fragment.

Proof sketch: By induction on the typing derivation for e.

  • typing_var_c case: Variables don't step.


  • typing_abs_c case: Abstractions don't step.


  • typing_app_c case: By case analysis on how e steps. The eval_beta case is interesting, since it follows by the substitution lemma. The others follow directly from the induction hypotheses.


Lemma preservation_c : ∀ (E : env) e e' T,
  typing_c E e T
  eval_c e e'
  typing_c E e' T.

Progress


Exercise



Complete the proof of the progress lemma. The induction hypothesis has already been appropriately generalized by the given proof fragment.

Proof sketch: By induction on the typing derivation for e.

  • typing_var_c case: Can't happen; the empty environment doesn't bind anything.


  • typing_abs_c case: Abstractions are values.


  • typing_app_c case: Applications reduce. The result follows from an exhaustive case analysis on whether the two components of the application step or are values and the fact that a value must be an abstraction.


Lemma progress_c : ∀ e T,
  typing_c nil e T
  value_c e ∨ ∃ e', eval_c e e'.


Renaming




Lemma typing_c_uniq : ∀ E e T,
  typing_c E e Tuniq E.


Lemma typing_c_rename : ∀ (x y : atom) E e T1 T2,
  x `notin` fv ey `notin` (dom E `union` fv e) →
  typing_c ((x ~ T1) ++ E) (open e x) T2
  typing_c ((y ~ T1) ++ E) (open e y) T2.


Exists-Fresh Definitions



Inductive typing : envexptypProp :=
  | typing_var : ∀ E (x : atom) T,
      uniq E
      binds x T E
      typing E (fvar x) T
  | typing_abs : ∀ E e T1 T2 (x : atom),
      x `notin` fv e
      typing ((x ~ T1) ++ E) (open e x) T2
      typing E (abs T1 e) (typ_arrow T1 T2)
  | typing_app : ∀ E e1 e2 T1 T2,
      typing E e1 (typ_arrow T1 T2) →
      typing E e2 T1
      typing E (app e1 e2) T2.

Inductive value : expProp :=
  | value_abs : ∀ e T1,
      lc (abs T1 e) →
      value (abs T1 e).

Inductive eval : expexpProp :=
  | eval_beta : ∀ e1 e2 T1,
      lc (abs T1 e1) →
      value e2
      eval (app (abs T1 e1) e2) (open e1 e2)
  | eval_app_1 : ∀ e1 e1' e2,
      lc e2
      eval e1 e1'
      eval (app e1 e2) (app e1' e2)
  | eval_app_2 : ∀ e1 e2 e2',
      value e1
      eval e2 e2'
      eval (app e1 e2) (app e1 e2').

Hint Constructors typing value eval.

Equivalence of Exists-Fresh and Cofinite Definitions



First, we show that the two local closure predicates are equivalent. This lemma follows via a renaming lemma for lc_c.
Lemma lc_rename : ∀ e (x:atom) (y:atom),
  x `notin` fv e
  lc_c (open e x) → lc_c (open e y).

Lemma lc_to_lc_c : ∀ e, lc elc_c e.

Lemma lc_c_to_lc : ∀ e, lc_c elc e.


Correspondence of typing and typing_c

Lemma typing_to_typing_c : ∀ E e T,
  typing E e Ttyping_c E e T.

Lemma typing_c_to_typing : ∀ E e T,
  typing_c E e Ttyping E e T.

Lemma value_to_value_c : ∀ e,
  value evalue_c e.

Lemma value_c_to_value : ∀ e,
  value_c evalue e.

Lemma eval_to_eval_c : ∀ e e',
  eval e e'eval_c e e'.

Lemma eval_c_to_eval : ∀ e e',
  eval_c e e'eval e e'.

Lemma preservation : ∀ E e e' T,
  typing E e T
  eval e e'
  typing E e' T.



Lemma progress : ∀ e T,
  typing nil e T
  value e ∨ ∃ e', eval e e'.



Regularity


To be fully confident about the exist-fresh rules, we can show their "regularity" --- that these relations only include locally closed terms. (For the exists-fresh definition of local closure.)

Importantly: to prove regularity, we need to know subst_lc, and that we most directly prove by using the connection between lc and lc_c.
Lemma subst_lc : ∀ (x : atom) u e,
  lc e
  lc u
  lc ([x ~> u] e).

Take-home exercise. open_abs is the analogue of subst_lc for bound variable substitution. HINT: use inversion, subst_intro and subst_lc to prove this lemma.
Lemma open_abs : ∀ e u T1,
  lc (abs T1 e) →
  lc u
  lc (open e u).


Lemma value_to_lc : ∀ e,
  value elc e.


Lemma eval_to_lc : ∀ e1 e2,
  eval e1 e2lc e1lc e2.


Lemma typing_to_lc : ∀ E e T,
  typing E e Tlc e.


Decidability of Typechecking



Equality on types is decidable
Lemma eq_typ_dec : ∀ (T T' : typ),
  { T = T' } + { TT' }.

Take-home exercise.

To prove that ill-formed terms cannot be typechecked, we will need an auxiliary lemma that says that each term only has a single type.

HINT: to prove this lemma you will need to generalize the induction hypothesis for T2 and use the lemma binds_unique from AtomEnv.v.
Lemma typing_c_unique : ∀ E e T1 T2,
  typing_c E e T1
  typing_c E e T2
  T1 = T2.

Definition decidable (P : Prop) := (P ∨ ~ P).

Lemma typing_c_decidable : ∀ E e,
  lc_c euniq Edecidable (∃ T, typing_c E e T).

      Check (typing_abs_c L E (abs T e) T S).



Qed.


Lemma typing_weakening_strengthened : ∀ E F G e T,
  typing (G ++ E) e T
  uniq (G ++ F ++ E) →
  typing (G ++ F ++ E) e T.




This page has been generated by coqdoc