Library nbe
We will develop a complete formalization of simply-typed lambda-calculus with constants in Coq, using the Program extension to write dependently-typed programs on inductive families. The development uses a pure de Bruijn encoding and dependently-typed abstract syntax. It includes the theory of lifting and substitution, a denotational semantics given by interpreting terms directly into Coq, and finally a proof of weak normalization for the call-by name strategy.
This paper describes all the technical parts of the development. It is a literate coqdoc script, missing only the proof scripts.
First we define a set of tactics that will be used later.
Rewrite using uniqueness of indentity proofs
H = refl_equal X.
Ltac simpl_uip :=
match goal with
[ H : ?X = ?X |- _ ] => rewrite (UIP_refl _ _ H) in *; clear H
end.
Try to abstract a proof of equality, if no proof of the same equality is present in the context.
Ltac abstract_eq_hyp H' p :=
match type of p with
?X = ?Y =>
match goal with
| [ H : X = Y |- _ ] => fail 1
| _ => set (H':=p) ; try (change p with H') ; clearbody H'
end
end.
Apply the tactic tac to proofs of equality appearing as coercion arguments.
Ltac on_coerce_proof tac :=
match goal with
[ |- ?T ] =>
match T with
| context [ eq_rect _ _ _ _ ?p ] => tac p
end
end.
Abstract proofs of equalities of coercions.
Ltac abstract_eq_proof := on_coerce_proof ltac:(fun p => let H := fresh "eqH" in abstract_eq_hyp H p).
Factorize proofs, by using proof irrelevance so that two proofs of the same equality
in the goal become convertible.
Ltac pi_eq_proof_hyp p :=
match type of p with
?X = ?Y =>
match goal with
| [ H : X = Y |- _ ] =>
match p with
| H => fail 2
| _ => rewrite (proof_irrelevance (X = Y) p H)
end
| _ => fail " No hypothesis with same type "
end
end.
Factorize proofs of equality appearing as coercion arguments.
Ltac pi_eq_proof := on_coerce_proof pi_eq_proof_hyp.
Clear unused reflexivity proofs.
Ltac clear_refl_eq :=
match goal with [ H : ?X = ?X |- _ ] => clear H end.
Ltac clear_refl_eqs := repeat clear_refl_eq.
Clear unused equality proofs.
Ltac clear_eq :=
match goal with [ H : _ = _ |- _ ] => clear H end.
Ltac clear_eqs := repeat clear_eq.
Everything is done in implicit arguments mode.
Module Type Constants.
The set of constant types.
Parameter constant_types : Set.
The constants themselves.
Parameter constants : Set.
A typing function for constants.
Parameter type_constant : constants -> constant_types.
End Constants.
The implementation.
The functor
L implements typing, denotational semantics and normalization of simply-typed
lambda-calculus plus constants C.
Module L (C : Constants).
Import C.
The types are just atoms or the arrow.
Inductive type : Set :=
| type_cst : constant_types -> type
| type_arrow : type -> type -> type.
Coercion type_cst : constant_types >-> type.
Infix " --> " := type_arrow (at level 20).
A context is either empty or a context and a variable.
Contexts are effectively snoc-lists, hence the new inductive.
Inductive ctx : Set :=
| empty : ctx
| comma : ctx -> type -> ctx.
Infix ":," := comma (at level 40, left associativity).
Appending one context to another, by induction on the second one.
Fixpoint append (G D : ctx) { struct D } : ctx :=
match D with
| empty => G
| D' :, x => (append G D') :, x
end.
Hint Unfold append.
Infix ";" := append (right associativity, at level 60).
Due to the way append is defined, these two are not convertible.
Lemma app_empty : forall G, (empty ; G) = G.
Associativity of context appending.
Lemma app_assoc : forall G D L, G ; D ; L = (G ; D) ; L.
Hint Rewrite app_empty app_assoc : app.
Opaque app_assoc.
Simplification tactic using the previous lemmas.
Ltac my_simpl := program_simpl ; try simpl_JMeq ; autorewrite with app ; program_simpl.
Variables are encoded as typed de Bruijn indices.
Inductive var : ctx -> type -> Set :=
| first : forall G T, var (G :, T) T
| next : forall G T, var G T -> forall U, var (G :, U) T.
The AST of well-typed terms.
Constants get their type from the
type_constant function.
A rel just embeds a variable. The abstraction constructor
lam contains a body typed in an extended environment.
Application applies only functions to objects having their domain type.
Inductive term (G : ctx) : type -> Set :=
| cst : forall c : constants, term G (type_constant c)
| rel : forall T, var G T -> term G T
| lam : forall (T T' : type),
term (G :, T) T' -> term G (T --> T')
| app : forall T T' : type,
term G (T --> T') -> term G T -> term G T'.
The really dependent scheme for term. Not used in the development but may be useful.
Scheme term_dep := Induction for term Sort Prop.
Tactic to automatically invert contradictory hypotheses.
Ltac try_inversion :=
match goal with
| H : _ = _ |- _ => inversion H ; auto
| H : var empty _ |- _ => inversion H
end.
Use these for solving obligations from now on.
Obligations Tactic := my_simpl ; try try_inversion ; try omega.
The next two functions reindex variables and terms in
G to a new context G'
with respect to an equality between G and G'. These are dynamic coercions in the sense
that they can be reduced when applied to a constructed term, contrary to the
substitution principle eq_rect.
Program Fixpoint coerce_var_context G T (v : var G T) G' (H : G = G')
{ struct v } : var G' T :=
match G' with
| empty => !
| G'' :, V' =>
match v with
| first vG vT => first G'' V'
| next v'G v'T v' V =>
@next G'' v'T (coerce_var_context v' _) V'
end
end.
Program Fixpoint coerce_term_context G T (t : term G T) G' (H : G = G')
{ struct t } : term G' T :=
match t with
| cst c => cst G' c
| rel T v => rel (coerce_var_context v H)
| lam tau tau' b => @lam G' _ _ (@coerce_term_context (G :, tau) tau' b (G' :, tau) _)
| app tau tau' f e => @app G' tau tau' (coerce_term_context f H)
(coerce_term_context e H)
end.
Like
eq_rect, coerce reduces to the identity when G and G' are convertible.
Lemma coerce_var_context_id : forall (G : ctx) T (v : var G T),
coerce_var_context v (refl_equal G) = v.
Lemma coerce_term_context_id : forall (G : ctx) T (t : term G T),
coerce_term_context t (refl_equal G) = t.
We now define some tactics that help to deal with coercions. They work in multiple
passes. First, we abstract any equality proof appearing in the goal as an argument of
a coercion, so as not to polute it with irrelevant proof terms. Then we factorize the proofs
and normalize the term using proof irrelevance so that two references to the same equality proof
are the same. Next we try to simplify the equations using Streicher's axiom K
(a proof
p of x = x can be substituted by refl_equal x). Finally we try to rewrite the term
using the previous lemmas about coerce and refl_equal. This procedure is very effective,
most of the coercions can be eliminated this way. We get stuck only when an induction is needed.
Abstract a proof of equality, if none is already present in the context.
Ltac on_coerce_proof tac :=
match goal with
[ |- ?T ] =>
match T with
| context [ coerce_var_context _ ?p ] =>
tac p
| context [ coerce_term_context _ ?p ] =>
tac p
| context [ eq_rect _ _ _ _ ?p ] =>
tac p
end
end.
Ltac abstract_coerce_proof := on_coerce_proof
ltac:(fun p =>
let H := fresh "coerceH" in
abstract_eq_hyp H p).
Ltac abstract_coerce_proofs := repeat abstract_coerce_proof.
Use proof-irrelevance to eliminate remaining non-abstracted proofs.
Ltac pi_coerce_proof := on_coerce_proof ltac:(pi_eq_proof_hyp).
Ltac pi_coerce_proofs := repeat pi_coerce_proof.
The two preceding tactics in sequence.
Ltac clear_coerce_proofs :=
abstract_coerce_proofs ; pi_coerce_proofs.
Rewrite
coerce_* t (refl_equal _) to t.
Hint Rewrite coerce_term_context_id coerce_var_context_id : coerce_id.
Ltac rewrite_coerce_id := autorewrite with coerce_id.
Clear the context and term of equality proofs.
Ltac clear_coerce_ctx :=
rewrite_coerce_id ; clear_coerce_proofs.
Reapeated elimination of
eq_rect applications.
Abstracting equalities makes it run much faster than an naive implementation.
Ltac simpl_eqs :=
repeat (elim_eq_rect ; simpl ; clear_coerce_ctx).
Combine all the tactics to simplify goals containing coercions.
Ltac simpl_term :=
simpl ; simpl_eqs ; clear_coerce_ctx ; clear_refl_eqs ;
try subst ; simpl ; repeat simpl_uip ; rewrite_coerce_id.
In fact
eq_rect and coerce are always equal, so we can switch between
frozen and dynamic coercions at will.
Lemma eq_rect_coerce : forall G T (t : term G T) G' (Heq: G = G'),
eq_rect _ (fun G' => term G' T) t _ Heq = coerce_term_context t Heq.
Lemma eq_rect_coerce_var : forall G T (t : var G T) G' (Heq: G = G'),
eq_rect _ (fun G' => var G' T) t _ Heq = coerce_var_context t Heq.
Tactic to change
eq_rect applications to coercions.
Allows to reduce coercions sometimes.
Hint Rewrite eq_rect_coerce eq_rect_coerce_var : eq_rect_coerce.
Ltac rew_eq_coerce := autorewrite with eq_rect_coerce.
Lifting and substitution.
We now define lifting and substitution of terms. The specifications of the functions are exactly the lemmas you would expect.
Section Substitution.
Lifting a variable
t in context G ; D by U to get a variable in G :, U ; D.
The context D represents the bindings we must avoid to capture.
Program Fixpoint lift_var (G D : ctx) (T : type) (t : var (G ; D) T)
(U : type) {struct t} : var (G :, U ; D) T :=
match D with
| empty => next t U
| D' :, _ =>
match t with
| first G' _ => first (G :, U ; D') T
| next G' T' v V' => next (lift_var G D' v U) V'
end
end.
We prove the defining equations as lemmas.
Lemma lift_var_empty : forall G T U (v : var G T), lift_var G empty v U = next v U.
Lifting a term by a type
U is just a fold.
Program Fixpoint lift_rec G D T (t : term (G ; D) T) U
{struct t}: term (G :, U ; D) T :=
match t with
| cst c => cst _ c
| rel T v => rel (lift_var G D v U)
| lam tau tau' b => lam (lift_rec G (D :, tau) b U)
| app tau tau' f e => app (lift_rec G D f U) (lift_rec G D e U)
end.
The non recursive version, a.k.a. weakening.
Program Definition lift (G : ctx) T (t : term G T) U : term (G :, U) T :=
lift_rec G empty t U.
The defining equations for the variable case.
Lemma lift_rec_empty : forall G T U (v : var G T),
lift_rec G empty (rel v) U = rel (next v U).
Lemma lift_rec_comma_first : forall G T U D,
lift_rec G (D :, T) (rel (first (G ; D) T)) U = rel (first (G :, U ; D) T).
Lemma lift_rec_comma_next : forall G T U D T' (v : var (G ; D) T'),
lift_rec G (D :, T) (rel (next v T)) U = rel (next (lift_var _ _ v U) T).
Lifting by a context is lifting by each variable in turn.
Program Fixpoint lift_ctx (G D : ctx) T (t : term G T)
{ struct D } : term (G ; D) T :=
match D with
| empty => t
| D' :, U => let t' := lift_ctx D' t in
lift t' U
end.
Substitution of a variable by a term in context
G, under a context D.
Program Fixpoint subst_var_rec G D T U (t : var (G :, T ; D) U)
(s : term G T) { struct D } : term (G ; D) U :=
match D with
| empty =>
match t with
| first tG tT => s (** substitution takes place *)
| next tG' tT' tv _ =>
rel tv (** unlift the var to account for subtitution *)
end
| D' :, V =>
match t with
| first tG tT => rel (first (G ; D') V) (** do nothing *)
| next tG' tT' tv V => (** substitute inside, then lift by one *)
lift (@subst_var_rec G D' T U tv s) V
end
end.
The defining equations.
Lemma subst_var_rec_empty_first : forall G T (s : term G T),
subst_var_rec empty (first G T) s = s.
Lemma subst_var_rec_empty_next : forall G T (v : var G T) T' (s : term G T'),
subst_var_rec empty (next v T') s = rel v.
Lemma subst_var_rec_comma_first : forall G T T' (t : term G T') D,
subst_var_rec (D :, T) (first (G :, T' ; D) T) t = rel (first (G ; D) T).
Lemma subst_var_rec_comma_next : forall G T T' (t : term G T') D
T'' (v : var (G :, T' ; D) T),
subst_var_rec (D :, T'') (next v T'') t = lift (subst_var_rec D v t) T''.
Non-recursive wrapper to substitute the first variable in the context.
Program Definition subst_var G T U (t : var (G :, T) U) (s : term G T) :
term G U := subst_var_rec empty t s.
Substitution in a term.
Program Fixpoint subst_rec G D T U (t : term (G :, T ; D) U)
(s : term G T) { struct t } : term (G ; D) U :=
match t with
| cst c => cst (G ; D) c
| rel _ v => @subst_var_rec G D T U v s
| lam tau tau' b =>
lam (subst_rec (D :, tau) b s)
| app tau tau' f e => app (subst_rec D f s) (subst_rec D e s)
end.
Wrapped again.
Program Definition subst (G : ctx) (T U : type) (t : term (G :, T) U)
(s : term G T) : term G U := subst_rec empty t s.
The defining equations.
Lemma subst_rec_empty_first : forall G T (t : term G T),
subst_rec empty (rel (first G T)) t = t.
Lemma subst_rec_empty_next : forall G T T' (t : term G T) (v : var G T'),
subst_rec empty (rel (next v T)) t = rel v.
Lemma subst_rec_comma_first : forall G T T' (t : term G T') D,
subst_rec (D :, T) (rel (first (G :, T' ; D) T)) t = rel (first (G ; D) T).
Lemma subst_rec_comma_next : forall G T T' (t : term G T') D
T'' (v : var (G :, T' ; D) T),
subst_rec (D :, T'') (rel (next v T'')) t = lift (subst_rec D (rel v) t) T''.
Commutation lemmas.
We now relate lifting and substitution to ultimately prove the substitution lemma.
Substituting for a variable just lifted is a no-op.
We first typecheck the statement, which will insert coercions around
We first typecheck the statement, which will insert coercions around
t to get the
right indices. The obligations are automatically resolved using the Heq hypothesis.
Note that we separate the equality so that induction can proceed on t of type term G' T
while still retaining the equation between G' and G ; D.
Program Definition subst_lift_rec_stmt :=
forall G' T (t : term G' T) G D T' (u : term G T') (Heq : G' = (G ; D)),
subst_rec D (lift_rec G D t T') u = t.
Now we prove it, by induction on
t. We first rewrite eq_rect to coercions, then simplify
the term using simpl_term. This takes care of almost all the plumbing.
Lemma subst_lift_rec : subst_lift_rec_stmt.
Opaque coerce_var_context.
Lemma subst_lift_var : forall G D T (v : var (G ; D) T) T' (u : term G T'),
subst_var_rec D (lift_var G D v T') u = rel v.
Lemma subst_lift : forall G D T (t : term (G ; D) T) T' (u : term G T'),
subst_rec D (lift_rec G D t T') u = t.
The real lemma is then just an instance of the previous one.
Lemma subst_lift : forall G D T (t : term (G ; D) T) T' (u : term G T'),
subst_rec D (lift_rec G D t T') u = t.
We now prove that equality coercion commute with lift to solve some of the later
lemmas which involve coercions of lift calls. Idealy this should be either easily derivable or
automatically generated by some kind of generic programming.
Lemma coerce_var_context_lift_var :
forall G'' T (t : var G'' T) G D (Heq:G'' = G ; D)
G' U (H : (G :, U ; D) = (G' :, U ; D)) (H' : G'' = (G' ; D)),
coerce_var_context (lift_var G D (coerce_var_context t Heq) U) H =
lift_var G' D (coerce_var_context t H') U.
Lemma coerce_term_context_lift_rec_aux :
forall G'' T (t : term G'' T) G D (Heq:G'' = G ; D) G' U
(H : (G :, U ; D) = (G' :, U ; D)) (H' : G'' = (G' ; D)),
coerce_term_context (lift_rec G D (coerce_term_context t Heq) U) H =
lift_rec G' D (coerce_term_context t H') U.
Lemma coerce_term_context_lift_rec :
forall G D T (t : term (G ; D) T) G' U (H : (G :, U ; D) = (G' :, U ; D))
(H' : (G ; D) = (G' ; D)),
coerce_term_context (lift_rec G D t U) H =
lift_rec G' D (coerce_term_context t H') U.
We have to take care of the opaqueness of these constants all the time, spurious unfoldings
can occur otherwise, greatly obfuscating the goal.
Opaque coerce_var_context coerce_term_context.
Opaque lift_rec subst_rec.
We continue on our road to the substitution lemma. Here we prove commutation of
lift_var.
Program Definition lift_lift_var_stmt :=
forall G' U (t : var G' U) G D D' (Heq : G' = G ; D ; D') T T',
lift_var (G :, T ; D) D' (lift_var G (D ; D') t T) T' =
lift_var G (D :, T' ; D') (lift_var (G ; D) D' t T') T.
Lemma lift_lift_var : lift_lift_var_stmt.
Program Definition lift_lift_stmt :=
forall G' U (t : term G' U) G D D' T T' (Heq: G' = G ; D ; D'),
lift_rec (G :, T ; D) D' (lift_rec G (D ; D') t T) T' =
lift_rec G (D :, T' ; D') (lift_rec (G ; D) D' t T') T.
Lemma lift_lift : lift_lift_stmt.
Commutation of lifting and substitution, for variables.
Program Definition subst_lift_var_comm_stmt :=
forall G' T (t : var G' T) G D' D U T' (s : term G U)
(Heq: G' = G :, U ; D ; D'),
subst_var_rec (D :, T' ; D') (lift_var (G :, U ; D) D' t T') s =
lift_rec (G ; D) D' (subst_var_rec (D ; D') t s) T'.
Commutation of lifting and substitution, for terms.
Program Definition subst_lift_comm_stmt :=
forall G' T (t : term G' T) G U D D' T' (s : term G U)
(Heq: G' = G :, U ; D ; D'),
subst_rec (D :, T' ; D') (lift_rec (G :, U ; D) D' t T') s =
lift_rec (G ; D) D' (subst_rec (D ; D') t s) T'.
The non-recursive case, with no
D' context. This time the statement need not
contain any coercions.
Lemma subst_lift_comm_full :
forall G U D T (t : term (G :, U ; D) T) (s : term G U) T',
subst_rec (D :, T') (lift_rec (G :, U ; D) empty t T') s =
lift_rec (G ; D) empty (subst_rec D t s) T'.
Finally, we can state the subtitution lemma as usual.
The proof uses all the lemmas proven above, including commutation of
lift_rec and
coerce_term_context.
Program Definition subst_rec_comm_stmt :=
forall G' T (u : term G' T) D' G D T' T''
(s : term (G :, T'' ; D) T') (t : term G T'')
(Heq: G' = (G :, T'' ; D :, T' ; D')),
subst_rec D' (subst_rec (D :, T' ; D') u t) (subst_rec D s t) =
subst_rec (D ; D') (subst_rec D' u s) t.
Lemma subst_rec_comm : subst_rec_comm_stmt.
Non-recursive case, where no coercion is needed anymore.
Lemma subst_comm :
forall G T'' D T' T (u : term (G :, T'' ; D :, T') T)
(s : term (G :, T'' ; D) T') (t : term G T''),
subst (subst_rec (D :, T') u t) (subst_rec D s t) =
subst_rec D (subst u s) t.
End Substitution.
Interpretation into CIC.
There is a direct embedding of into the Calculus of Constructions, which we build now.
Section Interpretation.
We require interpretations for the constants and their types.
Variable interp_cst_type : constant_types -> Set.
Variable interp_cst : forall c : constants, interp_cst_type (type_constant c).
Interpret a type into a coq
Set. We need an impredicate Set to work with this definition.
Fixpoint interp_type (t : type) : Set :=
match t with
| type_cst c => interp_cst_type c
| a --> b => interp_type a -> interp_type b
end.
A valuation is a function which associates a term to each variable in a context.
Program Definition valuation (G : ctx) :=
forall T, var G T -> interp_type T.
We can extend a valuation by a term.
Program Definition augment_valuation (G : ctx) (rho : valuation G)
(T : type) (x : interp_type T) : valuation (G :, T) :=
fun T v =>
match v with
| first _ _ => x
| next G' T' v' _ => rho T' v'
end.
Now we can interpret terms easily.
Fixpoint interp_term (G : ctx) (T : type) (t : term G T)
(v : valuation G) {struct t} : interp_type T :=
match t in term _ T return valuation G -> interp_type T with
| cst c => fun _ => interp_cst c
| rel T n => fun v => v T n
| lam T T' b => (fun v =>
(fun x : interp_type T =>
interp_term b (augment_valuation v x)))
| app T T' f e => fun v => (interp_term f v) (interp_term e v)
end v.
The empty valuation.
Program Definition nil_valuation : valuation empty := fun n v => !.
Interpretation of closed terms.
Definition interp_closed_term (T : type) (t : term empty T) : interp_type T :=
interp_term t nil_valuation.
End Interpretation.
A proof of weak normalization.
Inspired by , we build a proof of weak-head normalization using a tait-style proof. This is an instance of normalization by evaluation where the semantic domain is given by
R, the strong computability predicate.
Section WeakNormalization.
Beta-reduction and its contextual closure.
Inductive reduces (G : ctx) : forall T : type, term G T -> term G T -> Prop :=
| red_beta : forall tau tau' (b : term (G :, tau) tau') (e : term G tau),
reduces (app (lam b) e) (subst b e)
| red_app : forall tau tau' f f', @reduces G (tau --> tau') f f' ->
forall e, reduces (app f e) (app f' e).
Evaluation gives the definition of normal forms, either:
-
eval_transIf a termtreduces toswhich itself evaluates torthentevaluates tor. So evaluation is "backward-closed" by reduction. -
eval_lamLambda expressions are all in normal form. -
eval_cstA constant is a normal form.
Inductive evaluates (G : ctx) : forall T : type, term G T -> term G T -> Prop :=
| eval_trans : forall T (t : term G T) s r,
reduces t s -> evaluates s r -> evaluates t r
| eval_lam : forall tau tau' b, @evaluates G (tau --> tau') (lam b) (lam b)
| eval_cst : forall c, evaluates (cst G c) (cst G c).
R is the glueing model in the sense of Coquand et al. We have a syntactic part v which is
the value of the term t and a semantic one given by the function in the arrow case which ensures
closure by application. It is trivial for constants.
Note that R lives in Set so that we can extract it later.
Program Fixpoint R G T (t : term G T) { struct T } : Set :=
match T with
| type_cst c => { v : term G T | evaluates t v }
| tau --> tau' =>
({v : term G T | evaluates t v } *
(forall s, R s -> R (@app G tau tau' t s)))%type
end.
Extract the "syntactic" part.
Lemma R_eval : forall G T (t : term G T), R t -> { v : term G T | evaluates t v }.
Backward-closedness of evaluation extends to computability.
Lemma red_R_R : forall G T (t s : term G T), reduces t s -> R s -> R t.
Inductive substitution : ctx -> Set :=
| SNil : substitution empty
| SCons : forall G, substitution G -> forall T, term G T -> substitution (G :, T).
Substitute a term by a telescope.
Program Fixpoint mult_subst G D U (u : term (G ; D) U)
(s : substitution G) { struct s } : term D U :=
match s with
| SNil => u
| SCons G' s' T t =>
mult_subst D (subst_rec D u t) s'
end.
This predicate ensures
R-ness of all the terms in the telescope once they
are substituted by previous terms in the telescope.
It needs to be in Set as R is in Set.
Fixpoint substitution_R G (s : substitution G) { struct s } : Set :=
match s with
| SNil => unit
| SCons G' s' T t => (R (mult_subst empty t s') * substitution_R s')%type
end.
Inversion of non-empty substitutions.
Lemma substitution_cons_aux : forall G (s : substitution G), forall G' T, G = G' :, T ->
{ (s', t) : substitution G' * term G' T | JMeq s (SCons s' t) }.
Lemma substitution_cons : forall G T (s : substitution (G :, T)),
{ (s', t) : substitution G * term G T | s = SCons s' t }.
Substitution has no effect on constants.
Lemma subst_cst : forall G c (s : substitution G) D,
mult_subst D (cst (G ; D) c) s = cst D c.
Unfolding of telescope substitution on lambdas.
Lemma mult_subst_lam : forall G (s : substitution G) D T T' (u : term (G ; D :, T) T'),
mult_subst D (lam u) s = lam (mult_subst (D :, T) u s).
Unfolding of telescope substitution on applications.
Lemma mult_subst_app : forall G D T T' (f : term (G ; D) (T --> T'))
(e : term (G ; D) T) (s : substitution G),
mult_subst D (app f e) s = app (mult_subst D f s) (mult_subst D e s).
Commutation of multiple substitutions is needed to prove that
R is substitutive.
It is just an application of the substitution lemma proved previously.
Lemma mult_subst_comm : forall G (s : substitution G) D T' T
(u : term (G ; D :, T') T) (s0 : term (G ; D) T'),
subst (mult_subst (D :, T') u s) (mult_subst D s0 s) =
mult_subst D (subst u s0) s.
We extend
subst_lift to contexts using substitution of telescopes and lifting by contexts.
Program Definition mult_subst_lift_stmt :=
forall G (s : substitution G) T (t : term empty T),
(mult_subst empty (lift_ctx G t) s) = t.
We need to have a dynamic coercion to prove these lemmas.
Program Fixpoint coerce_subst_context G (s : substitution G)
G' (Heq:G = G') {struct s} : substitution G' :=
match G' with
| empty => SNil
| G' :, T' =>
match s with
| SNil => !
| SCons _ s' T t =>
@SCons G' (coerce_subst_context s' _) T' t
end
end.
Extension of the
on_coerce_proof tactical.
Ltac on_coerce_proof tac :=
match goal with
[ |- ?T ] =>
match T with
| context [ coerce_var_context _ ?p ] => tac p
| context [ coerce_term_context _ ?p ] => tac p
| context [ eq_rect _ _ _ _ ?p ] => tac p
| context [ coerce_subst_context _ ?p ] => tac p
end
end.
The rest is just reinstantiation of tactics with the new
on_coerce_proof.
We can finaly prove the mult_subst lemma.
Lemma mult_subst_lift : mult_subst_lift_stmt.
Main lemma.
A little lemma on variables is all that is missing before proving our main lemma. We can destruct variables, keeping the equality handy.
Lemma var_case_aux : forall G' T (v : var G' T), forall G t, G' = G :, t ->
(t = T /\ JMeq v (first G T)) + { v' : var G T | JMeq v (next v' t) }.
Lemma var_case : forall G t T (v : var (G :, t) T),
(t = T /\ JMeq v (first G T)) + { v' : var G T | v = next v' t }.
We now prove the main lemma, substitutivity of
R. Note that we end with a closed term,
escaping the problem of variables.
Lemma R_substitutive : forall G U (u : term G U) (s : substitution G),
substitution_R s -> R (mult_subst empty u s).
We get weak normalization as a trivial consequence of the previous lemma and
R_eval.
Lemma term_wn : forall T (t : term empty T), { v : term empty T | evaluates t v }.
End WeakNormalization.
Section StrongNormalization.
Fixpoint neutral G T (t : term G T) {struct t} : Prop :=
match t with
| cst _ => True
| rel _ _ => True
| lam _ _ _ => False
| app _ _ _ _ => True
end.
Inductive neutral (G : ctx) : type -> Set :=
| neutral_rel : forall T, var G T -> neutral G T
| neutral_app : forall T T', neutral G (T --> T') -> nf G T -> neutral G T'
with nf (G : ctx) : type -> Set :=
| nf_cst : forall c : constants, nf G (type_constant c)
| nf_neutral : forall T, neutral G T -> nf G T
| nf_lam : forall (T T' : type),
nf (G :, T) T' -> nf G (T --> T').
Scheme neutral_mutind := Induction for neutral Sort Prop
with nf_mutind := Induction for nf Sort Prop.
Fixpoint neutral_term G T (n : neutral G T) { struct n } : term G T :=
match n in neutral _ T return term G T with
| neutral_rel _ v => rel v
| neutral_app _ _ f e => app (neutral_term f) (nf_term e)
end
with nf_term G T (t : nf G T) { struct t } : term G T :=
match t in nf _ T return term G T with
| nf_cst c => cst G c
| nf_neutral _ v => neutral_term v
| nf_lam tau tau' b => lam (nf_term b)
end.
Coercion nf_term : nf >-> term.
Coercion neutral_term : neutral >-> term.
Beta-reduction and its contextual closure.
Evaluation gives the definition of normal forms, either:
-
eval_transIf a termtreduces toswhich itself evaluates torthentevaluates tor. So evaluation is "backward-closed" by reduction. -
eval_lamLambda expressions are all in normal form. -
eval_cstA constant is a normal form.
Reserved Notation " [ t # u ] ==> v " (at level 60).
Reserved Notation " t ==> n " (at level 60).
Inductive normalizes (G : ctx) : forall T : type, term G T -> nf G T -> Prop :=
| norm_cst : forall c, cst G c ==> nf_cst G c
| norm_rel : forall T (v : var G T), rel v ==> nf_neutral (neutral_rel v)
| norm_lam : forall tau tau' b b',
(b ==> b') -> (@normalizes G (tau --> tau') (lam b) (nf_lam b'))
| norm_app : forall tau tau' (f : term G (tau --> tau')) f' e e' v,
(f ==> f') -> (e ==> e') -> ([ f' # e' ] ==> v) ->
(app f e ==> v)
where " t ==> n " := (@normalizes _ _ t n)
with normalizes_app (G : ctx) : forall T T', nf G (T --> T') -> nf G T -> nf G T' -> Prop :=
| norm_app_rel : forall T T' (v : neutral G (T --> T')) (t' : nf G T),
[ nf_neutral v # t' ] ==> nf_neutral (neutral_app v t')
| norm_app_lam : forall tau tau' (b : nf (G :, tau) tau') (v : nf G tau) b',
(subst (nf_term b) (nf_term v) ==> b') -> ([ nf_lam b # v ] ==> b')
where " [ t # u ] ==> v " := (@normalizes_app _ _ _ t u v).
Definition is_normalizing G T (s : term G T) := exists s', s ==> s'.
Definition is_normalizing_app G T T' (s : nf G (T --> T')) s' := exists s'', [ s # s' ] ==> s''.
Scheme normalizes_mutind := Induction for normalizes Sort Prop
with normalizes_app_mutind := Induction for normalizes_app Sort Prop.
Ltac clear_exeq :=
match goal with
[ H : existT _ ?x _ = existT _ ?x _ |- _ ] =>
let Hinj := fresh "Hinj" in
assert (Hinj:=inj_pair2 _ _ _ _ _ H) ; clear H ; try subst
end.
Ltac clear_exeqs := repeat clear_exeq.
Lemma normalize_det : (forall G T (t : term G T) n, t ==> n -> forall n', t ==> n' -> n = n') /\
(forall G T T' (t : nf G (T --> T')) u n, [ t # u ] ==> n -> forall n', [ t # u ] ==> n' -> n = n').
Lemma normalize_term_det : (forall G T (t : term G T) n, t ==> n -> forall n', t ==> n' -> n = n').
Lemma normalize_app_det :
(forall G T T' (t : nf G (T --> T')) u n, [ t # u ] ==> n -> forall n', [ t # u ] ==> n' -> n = n').
Inductive conv (G : ctx) : forall T, term G T -> term G T -> Prop :=
| conv_lam : forall tau tau' b b', @conv (G :, tau) tau' b b' -> conv (lam b) (lam b')
| conv_app : forall tau tau' (f f' : term G (tau --> tau')) e e',
conv f f' -> conv e e' -> conv (app f e) (app f' e')
| conv_refl : forall T (t : term G T), conv t t
| conv_sym : forall T (t u : term G T), conv t u -> conv u t
| conv_trans : forall T (t u v : term G T), conv t u -> conv u v -> conv t v
| conv_red : forall T (t u : term G T), reduces t u -> conv t u.
Lemma normalize_correct :
(forall G T (t : term G T) n, t ==> n -> conv t n) /\
(forall G T T' (t : nf G (T --> T')) u n, [ t # u ] ==> n -> conv (app t u) n).
Lemma normalize_term_correct : forall G T (t : term G T) n, t ==> n -> conv t n.
Tactic Notation "subst" "by" constr(t) :=
let Hsubst := fresh "Hsubst" in
assert(Hsubst:=t) ; subst.
We need to have a dynamic coercion to prove these lemmas.
Program Fixpoint coerce_neutral_context G T (n : neutral G T)
G' (Heq:G = G') {struct n} : neutral G' T :=
match n in neutral _ T return neutral G' T with
| neutral_rel T v => @neutral_rel G' T v
| neutral_app T T' f e => @neutral_app G' T T' (coerce_neutral_context f Heq) (coerce_nf_context e Heq)
end
with coerce_nf_context G T (n : nf G T)
G' (Heq:G = G') {struct n} : nf G' T :=
match n in nf _ T return nf G' T with
| nf_cst c => nf_cst G' c
| nf_neutral T n => @nf_neutral G' T (coerce_neutral_context n Heq)
| nf_lam tau tau' b => @nf_lam G' tau tau' (coerce_nf_context b _)
end.
Extension of the
on_coerce_proof tactical.
Ltac on_coerce_proof tac :=
match goal with
[ |- ?T ] =>
match T with
| context [ coerce_var_context _ ?p ] => tac p
| context [ coerce_term_context _ ?p ] => tac p
| context [ eq_rect _ _ _ _ ?p ] => tac p
| context [ coerce_subst_context _ ?p ] => tac p
| context [ coerce_nf_context _ ?p ] => tac p
| context [ coerce_neutral_context _ ?p ] => tac p
end
end.
The rest is just reinstantiation of tactics with the new
on_coerce_proof.
Section Complete.
Hypothesis SN : forall G T (t : term G T), is_normalizing t.
Lemma normalizes_exp_stmt : forall G T (b b' : term G T),
forall c, b' ==> c -> reduces b b' -> b ==> c.
Program Definition normalizes_subst_stmt : forall G' T (b : term G' T),
forall G tau D, (G' = G :, tau ; D) ->
forall (e : term G tau) o, subst_rec D b e ==> o ->
forall b', b ==> b' -> subst_rec D (nf_term b') e ==> o.
Lemma normalizes_subst : normalizes_subst_stmt.
Lemma normalize_complete :
forall G T (t u : term G T), conv t u -> forall n o, t ==> n -> u ==> o -> n = o.
End Complete.
Fixpoint SCN G T { struct T } : nf G T -> Prop :=
match T return nf G T -> Prop with
| type_cst c => fun t => True
| tau --> tau' => fun t => forall u : nf G tau, SCN u -> exists n : nf G tau', ([ t # u ] ==> n) /\ SCN n
end
with SCN_neutral G T { struct T } : neutral G T -> Prop :=
match T return neutral G T -> Prop with
| type_cst c => fun t => True
| tau --> tau' => fun t => forall u : nf G tau, SCN u -> exists n : nf G tau', ([ nf_neutral t # u ] ==> n) /\ SCN n
end.
Definition SN G T (t : term G T) :=
exists n : nf G T, t ==> n /\ SCN n.
Fixpoint substitution_SN G (s : substitution G) { struct s } : Prop :=
match s with
| SNil => True
| SCons G' s' T t => (SN (mult_subst empty t s') * substitution_SN s')%type
end.
Lemma mult_subst_comma_first :
forall G (s : substitution G) D T, mult_subst (D :, T) (rel (first (G; D) T)) s = rel (first D T).
Program Definition strong_normalization_app_stmt :=
forall G U (t : nf G U), forall T T', (U = T --> T') -> forall (u : nf G T), SCN t -> SCN u ->
exists n' : nf G T', ([ t # u ] ==> n') /\ SCN n'.
Lemma strong_normalization_app : strong_normalization_app_stmt.
Lemma neutral_SCN : forall T G (n : neutral G T), SCN (nf_neutral n).
Program Definition SN_substitutive_stmt :=
forall G' U (u : term G' U) G D (H : G' = G ; D) (s : substitution G),
substitution_SN s -> SN (mult_subst D u s).
Lemma SN_substitutive : SN_substitutive_stmt.
Opaque subst_rec.
Transparent subst_rec.
Lemma strong_computability : forall T G (t : nf G T), SCN t.
Program Definition SCN_prop :=
forall G T (t : nf G T)
Program Definition SCN_subst_stmt := forall G T (n : nf G T),
SCN n -> forall (n' : nf G T), reduces n n' -> SCN n'.
Lemma SCN_subst : SCN_subst_stmt.
Extract the "syntactic" part.
Lemma SN_eval : forall G T (t : term G T), SN t -> { v : term G T | normalizes t v }.
Backward-closedness of evaluation extends to computability.
Lemma red_SN_SN : forall G T (t s : term G T), reduces t s -> SN s -> SN t.
All variables are in normal form.
Lemma SN_rel : forall G T (v : var G T), SN (rel v).
This predicate ensures
SN-ness of all the terms in the telescope once they
are substituted by previous terms in the telescope.
It needs to be in Set as SN is in Set.
Fixpoint substitution_SN G (s : substitution G) { struct s } : Set :=
match s with
| SNil => unit
| SCons G' s' T t => (SN (mult_subst empty t s') * substitution_SN s')%type
end.
Extension of the
on_coerce_proof tactical.
Ltac on_coerce_proof tac :=
match goal with
[ |- ?T ] =>
match T with
| context [ coerce_var_context _ ?p ] => tac p
| context [ coerce_term_context _ ?p ] => tac p
| context [ eq_rect _ _ _ _ ?p ] => tac p
| context [ coerce_subst_context _ ?p ] => tac p
end
end.
The rest is just reinstantiation of tactics with the new
on_coerce_proof.
We can finaly prove the mult_subst lemma.
Main lemma.
A little lemma on variables is all that is missing before proving our main lemma. We can destruct variables, keeping the equality handy.
We now prove the main lemma, substitutivity of
SN. Note that we end with a closed term,
escaping the problem of variables.
Program Definition SN_substitutive_stmt :=
forall G' U (u : term G' U) G D (H : G' = G ; D) (s : substitution G),
substitution_SN s -> SN (mult_subst D u s).
Lemma SN_substitutive : SN_substitutive_stmt.
Opaque subst_rec.
Transparent subst_rec.
Transparent subst_rec.
Lemma SN_substitutive : forall G U (u : term G U) (s : substitution G),
substitution_SN s -> SN (mult_subst empty u s).
We get weak normalization as a trivial consequence of the previous lemma and
SN_eval.
Lemma term_wn : forall T (t : term empty T), { v : term empty T | normalizes t v }.
End StrongNormalization.
End L.
Instantiation.
We will now instantiate
L with the naturals and booleans.
Our constants are naturals or booleans of Coq.
Inductive cst_types : Set := cst_nat_t | cst_bool_t.
Inductive csts : Set :=
| cst_nat : nat -> csts
| cst_bool : bool -> csts.
Definition type_constants (cst : csts) : cst_types :=
match cst with
| cst_nat _ => cst_nat_t
| cst_bool _ => cst_bool_t
end.
Module C.
Definition constant_types : Set := cst_types.
Definition constants : Set := csts.
Definition type_constant : constants -> constant_types := type_constants.
End C.
Module LambdaC := L C. Import LambdaC.
We construct trivial interpretations of the naturals and booleans in
Coq.
Definition interp_constant_type (cst_t : cst_types) : Set :=
match cst_t with
| cst_nat_t => nat
| cst_bool_t => bool
end.
Definition interp_constant (c : csts) : interp_constant_type (type_constants c) :=
match c return interp_constant_type (type_constants c) with
| cst_nat n => n
| cst_bool b => b
end.
Wrap interpretation using the previous functions.
Definition interp_nb_type := interp_type interp_constant_type.
Definition interp_nb := interp_term interp_constant.
Definition interp_closed_nb := interp_closed_term interp_constant_type interp_constant.
Definition nat_type : type := type_cst cst_nat_t.
The identity on naturals.
Program Definition nat_id_term : term empty (nat_type --> nat_type) :=
lam (rel (first empty nat_type)).
Eval compute in (interp_closed_nb nat_id_term).
Eval compute in (interp_type interp_constant_type (nat_type --> nat_type)).
We can extract the term normalization function from this development.
Extraction "nbe.ml" term_wn.