Built with Alectryon, running Coq+SerAPI v8.17.0+0.17.1. Bubbles () indicate interactive fragments: hover for details, tap to reveal contents. Use Ctrl+↑ Ctrl+↓ to navigate, Ctrl+🖱️ to focus. On Mac, use instead of Ctrl.

Scoped and sorted families

In this file we bootstrap the abstract theory of contexts, variables and substitution by defining a special case of type-families: families indexed by a set of contexts C and possibly a number of times by a set of types T.

Definition Fam₀ (T C : Type) := C -> Type .
Definition Fam₁ (T C : Type) := C -> T -> Type .
Definition Fam₂ (T C : Type) := C -> T -> T -> Type .

Additionally, to represent binders we use the following alternative to sorted families Fam₁. In this definition, the set of binders is indexed by the set of types T but not by the set of contexts C. Instead, we have a projection into C. This is important for reasons of bidirectional typing information flow. We could go with Fam₁ instead, but this would entail quite a bit of equality dance when manipulating it.

Record Oper T C := {
  o_op : T -> Type ;
  o_dom : forall x, o_op x -> C ;
}.

Now a first combinator: the formal cut of two families. This takes two 1-indexed families and returns the 0-indexed family consisting pairs of matching elements.

Record f_cut {T C} (F G : Fam₁ T C) (Γ : C) :=
  Cut { cut_ty : T ; cut_l : F Γ cut_ty ; cut_r : G Γ cut_ty }. 

We embed binder descriptions into sorted families, by dropping the context.

Definition bare_op {T C} (O : Oper T C) : Fam₁ T C := fun _ x => O x.

We define maps of these families, their identity and composition.

  Definition arr_fam₀ (F G : Fam₀ T C) := forall Γ, F Γ -> G Γ.
  Definition arr_fam₁ (F G : Fam₁ T C) := forall Γ x, F Γ x -> G Γ x.
  Definition arr_fam₂ (F G : Fam₂ T C) := forall Γ x y, F Γ x y -> G Γ x y.

  Notation "F ⇒₀ G" := (arr_fam₀ F G).
  Notation "F ⇒₁ G" := (arr_fam₁ F G).
  Notation "F ⇒₂ G" := (arr_fam₂ F G).

  Definition f_id₀ {F : Fam₀ T C} : F ⇒ F := fun _ a => a.
  Definition f_id₁ {F : Fam₁ T C} : F ⇒ F := fun _ _ a => a.
  Definition f_id₂ {F : Fam₂ T C} : F ⇒ F := fun _ _ _ a => a.

  Definition f_comp₀ {F G H : Fam₀ T C} (u : G ⇒ H) (v : F ⇒ G) : F ⇒ H
    := fun _ x => u _ (v _ x).
  Definition f_comp₁ {F G H : Fam₁ T C} (u : G ⇒ H) (v : F ⇒ G) : F ⇒ H
    := fun _ _ x => u _ _ (v _ _ x).
  Definition f_comp₂ {F G H : Fam₂ T C} (u : G ⇒ H) (v : F ⇒ G) : F ⇒ H
    := fun _ _ _ x => u _ _ _ (v _ _ _ x).
  Definition f_cut_map {F1 F2 G1 G2 : Fam₁ T C} (f : F1 ⇒ F2) (g : G1 ⇒ G2)
    : (f_cut F1 G1) ⇒ (f_cut F2 G2)
    := fun _ c => Cut (f _ _ c.(cut_l)) (g _ _ c.(cut_r)).

Now a bunch of notations for our constructions.

#[global] Notation "u ⦿₀ v" := (f_comp₀ u v).
#[global] Notation "u ⦿₁ v" := (f_comp₁ u v).
#[global] Notation "u ⦿₂ v" := (f_comp₂ u v).

#[global] Notation "F ∥ₛ G" := (f_cut F G).
#[global] Notation "u ⋅ v" := (Cut u v).

#[global] Notation "⦉ S ⦊" := (bare_op S) .