(** A compilation from pure Fsub to the Calculus of Constructions *)
(** Warning: You need to run Coq with the [-impredicative-set] flag to compile this file! *)
Set Implicit Arguments.
(** * The type language *)
(** Here's a generalization of the pure Fsub type language. As you can see, it actually includes the entirety of the [Set] universe. The idea is that each type stands for a particular [Set], and [Forall] is implemented using HOAS based on this idea. Because of Coq's positivity constraint for inductive definitions, it wouldn't be legal to change [Forall]'s second argument type to [ty -> ty]; [Set] is the best I know how to do. I'll prove the interesting theorems about this more general type language, and at the end I'll define a relation that exactly characterizes which [ty]s are true representations of pure Fsub types. *)
Inductive ty : Type :=
| TVar : Set -> ty
| Top : ty
| Arrow : ty -> ty -> ty
| Forall : ty -> (Set -> ty) -> ty.
(** Some syntactic abbreviations for particular [ty] constructors *)
Notation "@ x" := (TVar x) (at level 30).
Infix "-->" := Arrow (right associativity, at level 31).
Notation "'All' x <: t1 , t2" := (Forall t1 (fun x => t2)) (at level 32).
(** A compilation from types to [Set]s. The [Forall] case is the interesting one; this translation uses a "coercion-passing style", so a universally quantified type is translated into a function type that takes as arguments a [Set] and a coercion from it to the domain type. *)
Fixpoint tyDenote (t : ty) : Set :=
match t with
| TVar T => T
| Top => unit
| Arrow t1 t2 => tyDenote t1 -> tyDenote t2
| Forall t1 t2 => forall (T : Set), (T -> tyDenote t1) -> tyDenote (t2 T)
end.
(** * Subtyping *)
(** A context of subtyping facts about variables, which are represented as arbitrary [Set]s in HOAS style. Each fact gives the [Set], its bounding type, and a coercion into that type. *)
Inductive context : Type :=
| CNil : context
| CCons : forall (T : Set) (t : ty), (T -> tyDenote t) -> context -> context.
Notation "'__'" := CNil.
Notation "G ,, x <: t :: c" := (CCons (T := x) t c G) (at level 40).
(** Searching a context for a subtyping fact *)
Reserved Notation "[ x <: t :: c ] \in G" (at level 50).
Inductive lookup : context -> forall (T : Set) (t : ty), (T -> tyDenote t) -> Prop :=
| LHead : forall G x t c, [x <: t :: c] \in G,, x <: t :: c
| LTail : forall G x t c x' t' c', [x <: t :: c] \in G -> [x <: t :: c] \in G,, x' <: t' :: c'
where "[ x <: t :: c ] \in G" := (lookup G (T := x) t c).
(** The actual subtyping relation. In addition to the arguments from the written POPLmark spec, my judgment is also with respect to a coercion from the first type to the second. *)
Reserved Notation "G |-- t1 <: t2 :: c" (at level 50).
Inductive subTyp : context -> forall (t1 t2 : ty), (tyDenote t1 -> tyDenote t2) -> Prop :=
| SA_Top : forall G S,
G |-- S <: Top :: (fun _ => tt)
| SA_Refl_TVar : forall G X,
G |-- @X <: @X :: (fun x => x)
| SA_Trans_TVar : forall G X U T c1 c2,
[X <: U :: c1] \in G
-> G |-- U <: T :: c2
-> G |-- @X <: T :: (fun x => c2 (c1 x))
| SA_Arrow : forall G S1 S2 T1 T2 c1 c2,
G |-- T1 <: S1 :: c1
-> G |-- S2 <: T2 :: c2
-> G |-- S1 --> S2 <: T1 --> T2 :: (fun f x => c2 (f (c1 x)))
| SA_All : forall G S1 S2 T1 T2 c1 c2,
G |-- T1 <: S1 :: c1
-> (forall X c, G,, X <: T1 :: c |-- S2 X <: T2 X :: c2 X c)
-> G |-- Forall S1 S2 <: Forall T1 T2 :: (fun f X c => c2 X c (f X (fun x => c1 (c x))))
where "G |-- t1 <: t2 :: c" := (subTyp G t1 t2 c).
(** * Terms *)
(** Terms and typing judgments are combined in a single type, where the type of terms is indexed by a context and type to describe it. *)
Inductive tm : context -> ty -> Type :=
| Var : forall G t, tyDenote t -> tm G t
| Lam : forall G t1 t2, (tyDenote t1 -> tm G t2) -> tm G (t1 --> t2)
| App : forall G t1 t2, tm G (t1 --> t2) -> tm G t1 -> tm G t2
| TLam : forall G t1 t2, (forall X c, tm (G,, X <: t1 :: c) (t2 X)) -> tm G (Forall t1 t2)
| TApp : forall G t1 t2, tm G (Forall t1 t2) -> forall t c, G |-- t <: t1 :: c -> tm G (t2 (tyDenote t))
| Cast : forall G t, tm G t -> forall u c, G |-- t <: u :: c -> tm G u.
Notation "# x" := (Var _ _ x) (at level 33).
Infix "!" := App (left associativity, at level 34).
Notation "\ x :: t , e" := (Lam t (fun x => e)) (at level 35).
Notation "e ! [ c ]" := (TApp e c) (left associativity, at level 34).
Notation "\ x <: t :: c , e" := (TLam (t1 := t) _ (fun x c => e)) (at level 35).
Notation "{{ c }} e" := (Cast e c) (at level 36).
(** The compilation of a term to the Calculus of Constructions *)
Fixpoint tmDenote (G : context) (t : ty) (e : tm G t) {struct e} : tyDenote t :=
match e in (tm G t) return (tyDenote t) with
| Var _ _ v => v
| Lam _ _ _ e' => fun x => tmDenote (e' x)
| App _ _ _ e1 e2 => (tmDenote e1) (tmDenote e2)
| TLam _ _ _ e' => fun T c => tmDenote (e' T c)
| TApp _ _ _ e1 t2 c _ => (tmDenote e1) (tyDenote t2) c
| Cast _ _ e' _ c _ => c (tmDenote e')
end.
(** * The evaluation relation *)
(** A characterization of evaluation contexts for a term, where each context is associated with a type, a function from terms of that type to terms of the overall context's type, and an argument to which to apply that function to "fill in the hole" to regain the original term. *)
Inductive cont : forall G t, tm G t -> forall t', (tm G t' -> tm G t) -> tm G t' -> Prop :=
| Hole : forall G t (e : tm G t),
cont e (fun x => x) e
| AppFun : forall G t1 t2 (e1 : tm G (t1 --> t2)) e2 t' f (v : tm G t'),
cont e1 f v -> cont (e1 ! e2) (fun v => f v ! e2) v
| AppArg : forall G t1 t2 (e1 : tm G (t1 --> t2)) e2 t' f (v : tm G t'),
cont e2 f v -> cont (e1 ! e2) (fun v => e1 ! f v) v
| TypeFun : forall G t1 t2 (e : tm G (Forall t1 t2)) t c (Hst : _ |-- t <: _ :: c)
t' f (v : tm G t'),
cont e f v -> cont (e ! [Hst]) (fun v => f v ! [Hst]) v.
(** The overall single-step relation. You can see that it's allowed to step a term to a term in a different context. The [E_TappTabs] rule is to blame, as it adds an extra "garbage" element to the front of the result context. I'm not sure if there's a good way to avoid this, but it seems harmless. *)
Inductive step : forall G t, tm G t -> forall G', tm G' t -> Prop :=
| E_AppAbs : forall G T11 T12 t12 v2,
step (G := G) ((Lam T11 (t2 := T12) t12) ! v2) (t12 (tmDenote v2))
| E_TappTabs : forall G T11 T12 t12 T2 c (Hst : G |-- T2 <: T11 :: c),
step (TApp (TLam (t1 := T11) T12 t12) Hst) (t12 (tyDenote T2) c)
| E_Ctx : forall G t (e : tm G t) t' f (v : tm G t') v',
cont e f v -> step v v' -> step e (f v').
(** We can reassemble a term from a context and the current value of its hole. *)
Lemma cont_idem : forall G t (e : tm G t) t' f (v : tm G t'),
cont e f v -> e = f v.
induction 1; congruence.
Qed.
(** The functions returned by [cont] are well-behaved with respect to [tmDenote]. *)
Lemma cont_nonExotic : forall G t (e : tm G t) t' f (v : tm G t'),
cont e f v -> forall v1 v2, tmDenote v1 = tmDenote v2 -> tmDenote (f v1) = tmDenote (f v2).
induction 1; simpl; intuition;
rewrite (IHcont _ _ H0); trivial.
Qed.
(** [step] doesn't change the semantics of any term. I can use equality in the conclusion instead of an asymmetric step relation because Coq's definitional equality includes all of the reduction rules necessary to see that the two denotations are equivalent. We reason about Fsub terms in explicit time steps, while reasoning about CIC terms happens in a known strongly normalizing setting, so we can deal with equivalences. *)
Theorem stepOk : forall G t (e : tm G t) G' (e' : tm G' t), step e e' -> tmDenote e = tmDenote e'.
induction 1; trivial.
rewrite (cont_idem H);
apply (cont_nonExotic H); trivial.
Qed.
(** * Ruling out exotic terms *)
(** We might want to have types that stand exactly for "real" representations of pure Fsub types and terms. I'll define these types by way of relations that filter out the bad elements of the more general types we've been using so far. The basic idea is to analyze each type/term with respect to a list of [Set]s or variable values that are allowed to appear in it as arguments of variable constructors. *)
(** Lists of [Set]s *)
Inductive Sets : Type :=
| Snil : Sets
| Scons : Set -> Sets -> Sets.
(** Finding a [Set] in a list *)
Inductive InSets : Set -> Sets -> Prop :=
| Shead : forall T TS, InSets T (Scons T TS)
| Stail : forall T T' TS, InSets T TS -> InSets T (Scons T' TS).
(** Well-formedness of types *)
Inductive ty_nonExotic : Sets -> ty -> Prop :=
| NE_TVar : forall TS T,
InSets T TS
-> ty_nonExotic TS (@T)
| NE_Top : forall TS,
ty_nonExotic TS Top
| NE_Arrow : forall TS t1 t2,
ty_nonExotic TS t1
-> ty_nonExotic TS t2
-> ty_nonExotic TS (t1 --> t2)
| NE_Forall : forall TS t1 t2,
ty_nonExotic TS t1
-> (forall (X : Set), ty_nonExotic (Scons X TS) (t2 X))
-> ty_nonExotic TS (Forall t1 t2).
(** Lists of values from arbitrary [Set]s *)
Inductive Values : Type :=
| Vnil : Values
| Vcons : forall (S : Set), S -> Values -> Values.
(** Looking up a value in a list *)
Inductive InValues : forall (S : Set), S -> Values -> Prop :=
| Vhead : forall (S : Set) (v : S) VS, InValues v (Vcons v VS)
| Vtail : forall (S : Set) (v : S) (S' : Set) (v' : S') VS,
InValues v VS -> InValues v (Vcons v' VS).
(** Well-formedness of terms *)
Inductive tm_nonExotic : Sets -> Values -> forall G t, tm G t -> Prop :=
| NE_Var : forall TS VS G t (v : tyDenote t),
InValues v VS
-> tm_nonExotic (G := G) TS VS (#v)
| NE_Lam : forall TS VS G t (e' : _ -> tm G t),
(forall x, tm_nonExotic TS (Vcons x VS) (e' x))
-> tm_nonExotic TS VS (Lam t e')
| NE_App : forall TS VS G t1 t2 (e1 : tm G (t1 --> t2)) e2,
tm_nonExotic TS VS e1
-> tm_nonExotic TS VS e2
-> tm_nonExotic TS VS (e1 ! e2)
| NE_TLam : forall TS VS G t1 t2 (e' : forall X c, tm (G,, X <: t1 :: c) (t2 X)),
(forall X c, tm_nonExotic (Scons X TS) VS (e' X c))
-> tm_nonExotic TS VS (TLam (t1 := t1) _ e')
| NE_TApp : forall TS VS G t1 t2 (e : tm G (Forall t1 t2)) t c (Hst : G |-- t <: t1 :: c),
tm_nonExotic TS VS e
-> ty_nonExotic TS t
-> tm_nonExotic TS VS (e ! [Hst]).
(** Now we can build the more precise types as refinements of those defined earlier. *)
Definition closed_ty : Type := sigT (ty_nonExotic Snil).
Definition closed_tm (t : ty) : Type := sigT (tm_nonExotic Snil Vnil (G := __) (t := t)).
(** * An example: the polymorphic identity function *)
(** Here's the type of the polymorphic identity function. *)
Definition idTy : ty := All X <: Top, @X --> @X.
(** Now here's the function itself. It's a bit messy to write because we have to do some higher-order unification manually. *)
Definition id : tm __ idTy := TLam (G := __) (t1 := Top) (fun X => @X --> @X) (fun X c => \x :: @X, #x).
(** We want to prove that these are well-formed. Add the inference rules of the relevant judgments to the logic programming database first. *)
Hint Constructors InSets InValues ty_nonExotic tm_nonExotic.
(** We build a [closed_ty] from [idTy] and an automatically-generated proof of its well-formedness. *)
Definition idTy_nonExotic : closed_ty.
exists idTy.
unfold idTy.
auto.
Defined.
(** We build a [closed_tm idTy] from [id] and a mostly automatically-generated proof of its well-formedness. It's necessary to provide the first proof step manually, since it involves some manual higher-order unification. *)
Definition id_nonExotic : closed_tm idTy.
exists id.
unfold id.
apply (NE_TLam (TS := Snil) (VS := Vnil) (G := __) (t1 := Top)
(fun x : Set => Arrow (TVar x) (TVar x))); auto.
Defined.
(** Now we can compile [id] into CIC. *)
Definition id_compiled := Eval compute in tmDenote id.
(** We can take a look at the reduced version of the result. *)
Print id_compiled.
(** This outputs an OCaml program that defines the polymorphic identity function. *)
Extraction id_compiled.