module Cat.CartesianClosed.Free {ℓ} (S : λ-Signature ℓ) where
Free cartesian closed categories🔗
open λ-Signature S renaming (Ob to Node ; Hom to Edge) data Mor : Ty → Ty → Type ℓ private variable τ σ ρ : Ty f g h : Mor τ σ infixr 20 _`∘_ infixr 19 _`,_
We can define the free Cartesian closed category on a -signature directly as a higher inductive type. The objects of this category will be the simple types over 1 and the morphisms are generated by exactly the operations and equations which go into our definition of Cartesian closed category.
data Mor where `_ : ∀ {x y} → Edge x y → Mor x (` y) `id : Mor σ σ _`∘_ : Mor σ ρ → Mor τ σ → Mor τ ρ `idr : f `∘ `id ≡ f `idl : `id `∘ f ≡ f `assoc : f `∘ (g `∘ h) ≡ (f `∘ g) `∘ h `! : Mor τ `⊤ `!-η : (h : Mor τ `⊤) → `! ≡ h `π₁ : Mor (τ `× σ) τ `π₂ : Mor (τ `× σ) σ _`,_ : Mor τ σ → Mor τ ρ → Mor τ (σ `× ρ) `π₁β : `π₁ `∘ (f `, g) ≡ f `π₂β : `π₂ `∘ (f `, g) ≡ g `πη : f ≡ (`π₁ `∘ f `, `π₂ `∘ f) `ev : Mor ((τ `⇒ σ) `× τ) σ `ƛ : Mor (τ `× σ) ρ → Mor τ (σ `⇒ ρ) `ƛβ : `ev `∘ (`ƛ f `∘ `π₁ `, `id `∘ `π₂) ≡ f `ƛη : f ≡ `ƛ (`ev `∘ (f `∘ `π₁ `, `id `∘ `π₂)) squash : is-set (Mor τ σ)
We package all of these laws into the appropriate bundles in this <details>
tag.
instance H-Level-Mor : ∀ {x y n} → H-Level (Mor x y) (2 + n) H-Level-Mor = basic-instance 2 squash module _ where open Precategory Free-ccc : Precategory ℓ ℓ Free-ccc .Ob = Ty Free-ccc .Hom = Mor Free-ccc .Hom-set _ _ = squash Free-ccc .id = `id Free-ccc ._∘_ = _`∘_ Free-ccc .idr _ = `idr Free-ccc .idl _ = `idl Free-ccc .assoc _ _ _ = `assoc open Cartesian-closed open is-exponential open Exponential open is-product open Terminal open Product Free-products : has-products Free-ccc Free-products a b .apex = a `× b Free-products a b .π₁ = `π₁ Free-products a b .π₂ = `π₂ Free-products a b .has-is-product .⟨_,_⟩ f g = f `, g Free-products a b .has-is-product .π₁∘⟨⟩ = `π₁β Free-products a b .has-is-product .π₂∘⟨⟩ = `π₂β Free-products a b .has-is-product .unique p q = `πη ∙ ap₂ _`,_ p q Free-terminal : Terminal Free-ccc Free-terminal .top = `⊤ Free-terminal .has⊤ x = contr `! `!-η open Cartesian-category using (products ; terminal) Free-cartesian : Cartesian-category Free-ccc Free-cartesian .products = Free-products Free-cartesian .terminal = Free-terminal Free-closed : Cartesian-closed Free-ccc Free-cartesian Free-closed .has-exp A B .B^A = A `⇒ B Free-closed .has-exp A B .ev = `ev Free-closed .has-exp A B .has-is-exp .ƛ = `ƛ Free-closed .has-exp A B .has-is-exp .commutes m = `ƛβ Free-closed .has-exp A B .has-is-exp .unique m' x = `ƛη ∙ ap `ƛ x private module Syn = Cartesian-category Free-cartesian module Synᵐ = Monoidal-category (Cartesian-monoidal Free-cartesian)
Instead of phrasing the universal property as a form of initiality, we’ll state induction instead. While we could derive induction from initiality, stating the “traditional” universal property for a free CCC, this has a couple of issues when it comes to working in a homotopical proof assistant.
Firstly, the categories we’re using for the normalisation proof are
not strict, so we do not have a
proper 1-category of models within which we could say that Free-ccc
is initial. Even then, it is slightly
unreasonable to expect Cartesian closed functors to preserve the
structure up to identity.
We could set up a bicategory of Cartesian closed
categories, functors, and natural transformations between them, but this
would result in a significantly more annoying normalisation result,
saying that every Mor
phism is isomorphic
to the inclusion of a normal form.
Conversely, if we did require all the categories involved to be
strict (which would require exchanging the presheaf category for
something like a material set-valued presheaf category),
having normal forms that live in types identical to the inputs
is still not good enough, because now we either end up with a normal
form that is only related to the input over a PathP
, or we introduce a subst
that would not compute in
a useful way.
Stating an induction principle for Ty
and Mor
is simply a much more
sensible thing to do: we do not impose a strictness condition on the
categories we’re eliminating into by requiring models to live in a
1-category, and now the types guarantee that the results are
definitionally talking about the input types. However, since
Mor
has quite
a few constructors, the inputs to this induction principle are…
quite gnarly. We package them up using the language of displayed categories.
First, we require a displayed category
which gives us not only the motives for elimination, i.e. the
types of the conclusions, but also the methods (including the
equational methods) for `id
and _`∘_
.
module elim {o' ℓ'} {D : Displayed Free-ccc o' ℓ'}
Next, must have total products over the Cartesian structure of In non-displayed language, this says that the total category has products and the projection functor preserves them. But in displayed language, it says precisely that we can put together product diagrams in which are displayed over product diagrams in We also ask for a total terminal object in
(cart : Cartesian-over D Free-cartesian)
This makes
into a Cartesian category over
which in turn enables us to talk about total exponential objects in
Of course, these also provide methods we’re lacking, so we must also ask
for these, making
into a category Cartesian closed over
Finally, we can ask for an interpretation
of the base types in
(cco : Cartesian-closed-over D cart Free-closed) (f : (x : Node) → D ʻ (` x)) where
open Cartesian-closed-over D cart {Free-closed} cco open Cat.Displayed.Reasoning D open Cartesian-over cart
This lets us construct a section of the objects of i.e. a method that turns any type into an object
Ty-elim : (x : Ty) → D ʻ x Ty-elim `⊤ = top' Ty-elim (` x) = f x Ty-elim (τ `× σ) = Ty-elim τ ⊗₀' Ty-elim σ Ty-elim (τ `⇒ σ) = [ Ty-elim τ , Ty-elim σ ]'
Since the base terms in a
have an arbitrary Ty
as input (but a base type as
output), we can only state the type of our last method after defining
Ty-elim
. This
type is, however, natural: we ask for a map
lying over every basic term
base-method : Type _
base-method = ∀ {x y} (e : Edge x y) → Hom[ ` e ] (Ty-elim x) (f y)
private module _ (h : base-method) where
go : ∀ {x y} (m : Mor x y) → Hom[ m ] (Ty-elim x) (Ty-elim y)
By a long and uninteresting case bash, we can extend base-method
to
cover all of Mor
.
Ty
as input (but a base type as
output), we can only state the type of our last method after defining
Ty-elim
. This
type is, however, natural: we ask for a map
lying over every basic term
go (` x) = h x go `id = id' go (f `∘ g) = go f ∘' go g go (`idr {f = f} i) = idr' (go f) i go (`idl {f = f} i) = idl' (go f) i go (`assoc {f = f} {g = g} {h = h} i) = assoc' (go f) (go g) (go h) i go `! = !' go (`!-η e i) = !'-unique₂ {h = !'} {h' = go e} {p = `!-η e} i go `π₁ = π₁' go `π₂ = π₂' go (f `, g) = ⟨ go f , go g ⟩' go (`π₁β {f = f} {g = g} i) = π₁∘⟨⟩' {f' = go f} {g' = go g} i go (`π₂β {f = f} {g = g} i) = π₂∘⟨⟩' {f' = go f} {g' = go g} i go `ev = ev' go (`ƛ e) = ƛ' (go e)
Note that the following equational cases need slight corrections
(which we are free to do because Mor
is a set) between the path
constructors in Mor
and the
laws required for a Cartesian closed category.
go (`πη {f = f} i) = want i where have : PathP (λ i → Hom[ Syn.⟨⟩-unique refl refl i ] _ _) (go f) ⟨ π₁' ∘' go f , π₂' ∘' go f ⟩' have = ⟨⟩'-unique {other' = go f} refl refl want : PathP (λ i → Hom[ `πη i ] _ _) (go f) ⟨ π₁' ∘' go f , π₂' ∘' go f ⟩' want = cast[] have go (`ƛβ {f = f} i) = want i where want : PathP (λ i → Hom[ `ƛβ {f = f} i ] _ _) (ev' ∘' ⟨ ƛ' (go f) ∘' π₁' , id' ∘' π₂' ⟩') (go f) want = cast[] (commutes' (go f)) go (`ƛη {f = f} i) = want i where want : PathP (λ i → Hom[ `ƛη {f = f} i ] _ _) (go f) (ƛ' (ev' ∘' ⟨ go f ∘' π₁' , id' ∘' π₂' ⟩')) want = cast[] (ƛ'-unique {p = refl} (go f) refl) go (squash x y p q i j) = is-set→squarep (λ i j → Hom[ squash x y p q i j ]-set (Ty-elim _) (Ty-elim _)) (λ i → go x) (λ i → go (p i)) (λ i → go (q i)) (λ i → go y) i j open Section
Induction for Free-ccc
says that, if we have
such a “displayed Cartesian closed category”
and interpretations of the base types and base terms, then
has a section.
Free-ccc-elim : base-method → Section D Free-ccc-elim h .S₀ = Ty-elim Free-ccc-elim h .S₁ = go h Free-ccc-elim h .S-id = refl Free-ccc-elim h .S-∘ f g = refl
We can recover a more traditional initiality result for Free-ccc
by
following the general pattern for deriving initiality
from induction. Specifically, if
is a (non-displayed) Cartesian closed category, then the chaotic bifibration
provides the “generic” inputs to our induction principle, and supposing
we additionally have a model of
in
the resulting section is exactly a Cartesian closed functor
This proves weak initiality.
To prove uniqueness, supposing we have some other Cartesian closed functor we would define a category over where the objects over are isomorphisms and the “morphism” over is the naturality square Cartesian closure of would provide the data necessary for the displayed Cartesian closed structure. Then, assuming agrees with on base types and terms (i.e., it picks out the same in the section of over objects is then a family of isomorphisms and the section over morphisms is exactly naturality for this family.
As mentioned above, without an additional assumption that is also a strict category, and that preserves the Cartesian closed structure up to identity, this “pseudo”, or bicategorical initiality is the best we can do.
Intensional syntax🔗
Our objective is to prove that, if both the base types and base terms
in the lambda-signature
have decidable equality, then so do the objects and morphisms in
As Mor
is a
complicated higher-inductive type, this is essentially infeasible to do
‘by hand’ by a case bash, which works for ordinary indexed inductive
types because those have all disjoint constructors.
The strategy will instead be to show that Mor
is equivalent to some type
that we can write a case bash for. That is, we’ll write a
sound normalisation procedure for Mor
, which (by construction)
factors through some type that has decidable equality.2 Note that since Mor
is a
quotient, and our normalisation procedure is a function, completeness is
automatic.
Factoring the construction through some intermediate type of normal
forms is also necessary to actually state normalisation to begin with:
soundness and completeness together imply that, if normalisation were
just an endofunction on Mor
, it would be the
identity.
Our notion of normal form is that of the simply-typed lambda
calculus. Note that despite Mor
working over
types, we will introduce a notion of context to be
used during the proof of normalisation. The normal form of a map
will then live in
i.e. it will inhabit the context with a single variable typed the domain
of
This is because normalisation for function types will involve
renamings, and implementing this correctly is easier if binders
are separate from products. Since we’re working with simple types,
contexts are just lists of types. We also have the usual definition of
variable as a pointer into a nonempty context.
data Cx : Type ℓ where ∅ : Cx _,_ : Cx → Ty → Cx private variable Γ Δ Θ : Cx data Var : Cx → Ty → Type ℓ where stop : Var (Γ , τ) τ pop : Var Γ τ → Var (Γ , σ) τ
The type of normal forms has a unique constructor for each type,3 and the arguments are essentially unsurprising in each case: There’s nothing to a normal form of the unit type, and a normal pair is a pair of normal forms. For function types, we shift the domain into the context. At base types, the normal forms are given by neutral terms.
data Nf where lam : Nf (Γ , τ) σ → Nf Γ (τ `⇒ σ) pair : Nf Γ τ → Nf Γ σ → Nf Γ (τ `× σ) unit : Nf Γ `⊤ ne : ∀ {x} → Ne Γ (` x) → Nf Γ (` x)
A neutral term is a variable, or an elimination form applied to another neutral. The “elimination form” for a base term is also application to a normal form in the domain. For example, we can build a neutral of type by projecting the first component from a neutral of type
data Ne where var : Var Γ τ → Ne Γ τ app : Ne Γ (τ `⇒ σ) → Nf Γ τ → Ne Γ σ hom : ∀ {a b} → Edge a b → Nf Γ a → Ne Γ (` b) fstₙ : Ne Γ (τ `× σ) → Ne Γ τ sndₙ : Ne Γ (τ `× σ) → Ne Γ σ
We summarize neutrals and normals with a selection of some of our finest “gammas and turnstiles”, eliding the translation4 from named variables in the context (i.e. the judgement to de Bruijn indices.
Normalisation and presheaves🔗
The key idea behind our normalisation algorithm, following Čubrić et. al. (1998), is to work in a category of presheaves over the syntax which are a model of the same with the interpretation of base types (and terms) given by the Yoneda embedding We thus have a (Cartesian closed) functor arising from initiality, which is defined in terms of the Cartesian closed structure of i.e. we have the following (definitional) computation rules.
The elements of thus do not correspond directly to morphisms in the base, but are instead a sort of interaction representation of Now since the Yoneda embedding is a Cartesian closed functor, the universal property of gives a natural isomorphism Intensionally, round-tripping through this isomorphism has the effect of replacing a map by its normal form, which can be noticed because (e.g.) the elements of are actual pairs. However, we can not observe this happening mathematically because, as mentioned above, a map and its normal form in are identical.
We would like to factor this isomorphism through Nf
above, but
Nf
is not
readily made into a presheaf on the entire syntax. We instead pass to
presheaves on a subcategory of the syntax where we can easily
internalise Nf
and Ne
: the
renamings, the smallest subcategory of
which is closed under precomposition by
and under the functorial action
which is of course best defined as the inductive type Ren
, which we
use as the type of morphisms for the category Rens
.
data Ren : Cx → Cx → Type ℓ where stop : Ren Γ Γ drop : Ren Γ Δ → Ren (Γ , τ) Δ keep : Ren Γ Δ → Ren (Γ , τ) (Δ , τ)
There is an evident embedding of back into written out below, by interpreting the constructors as the aforementioned categorical constructions (and contexts by products). Moreover, this embedding is a Cartesian functor.
⟦_⟧ᶜ : Cx → Ty ⟦ ∅ ⟧ᶜ = `⊤ ⟦ Γ , x ⟧ᶜ = ⟦ Γ ⟧ᶜ `× x ⟦_⟧ʳ : Ren Γ Δ → Mor ⟦ Γ ⟧ᶜ ⟦ Δ ⟧ᶜ ⟦ stop ⟧ʳ = `id ⟦ drop r ⟧ʳ = ⟦ r ⟧ʳ `∘ `π₁ ⟦ keep r ⟧ʳ = ⟦ r ⟧ʳ `∘ `π₁ `, `π₂
We then have to prove that all of these constructions are sets, since we will be
using them to form precategories and presheaves. This is bog-standard
encode-decode stuff, and so it’s uncommented in this <details>
block for space.
While we’re here, we also prove that all these type formers preserve
decidability of equality.
<details>
block for space.private same-cx : Cx → Cx → Prop ℓ same-cx ∅ ∅ = el! (Lift _ ⊤) same-cx ∅ _ = el! (Lift _ ⊥) same-cx (Γ , τ) (Δ , σ) = el! (⌞ same-cx Γ Δ ⌟ × ⌞ same-ty τ σ ⌟) same-cx (Γ , τ) _ = el! (Lift _ ⊥) from-same-cx : ∀ Γ Γ' → ⌞ same-cx Γ Γ' ⌟ → Γ ≡ Γ' from-same-cx ∅ ∅ p = refl from-same-cx (Γ , x) (Γ' , y) p = ap₂ _,_ (from-same-cx Γ Γ' (p .fst)) (from-same-ty x y (p .snd)) refl-same-cx : ∀ Γ → ⌞ same-cx Γ Γ ⌟ refl-same-cx ∅ = lift tt refl-same-cx (Γ , x) = (refl-same-cx Γ) , (refl-same-ty x) instance H-Level-Cx : ∀ {n} → H-Level Cx (2 + n) H-Level-Cx = basic-instance 2 $ set-identity-system→hlevel (λ x y → ⌞ same-cx x y ⌟) refl-same-cx (λ x y → hlevel 1) from-same-cx private same-ren : ∀ {Γ' Δ'} (p : ⌞ same-cx Γ Γ' ⌟) (q : ⌞ same-cx Δ Δ' ⌟) → Ren Γ Δ → Ren Γ' Δ' → Prop ℓ same-ren p q stop stop = el! (Lift _ ⊤) same-ren p q (drop x) (drop y) = same-ren (p .fst) q x y same-ren p q (keep x) (keep y) = same-ren (p .fst) (q .fst) x y same-ren p q stop (drop y) = el! (Lift _ ⊥) same-ren p q stop (keep y) = el! (Lift _ ⊥) same-ren p q (drop x) stop = el! (Lift _ ⊥) same-ren p q (drop x) (keep y) = el! (Lift _ ⊥) same-ren p q (keep x) stop = el! (Lift _ ⊥) same-ren p q (keep x) (drop y) = el! (Lift _ ⊥) from-same-ren : ∀ {Γ Δ Γ' Δ'} (p : ⌞ same-cx Γ Γ' ⌟) (q : ⌞ same-cx Δ Δ' ⌟) x y → ⌞ same-ren p q x y ⌟ → PathP (λ i → Ren (from-same-cx Γ Γ' p i) (from-same-cx Δ Δ' q i)) x y from-same-ren {Γ = Γ} {Γ' = Γ'} p q stop stop α = is-set→cast-pathp {x = Γ , Γ} {y = Γ' , Γ'} {from-same-cx _ _ p ,ₚ _} {_ ,ₚ _} (uncurry Ren) (hlevel 2) λ i → stop from-same-ren p q (drop x) (drop y) α = λ i → drop (from-same-ren _ _ x y α i) from-same-ren {Γ , τ} {Δ , τ} {Γ' , τ'} {Δ' , τ'} p q (keep x) (keep y) α = is-set→cast-pathp {x = _} {y = _} {from-same-cx _ _ (p .fst , q .snd) ,ₚ from-same-cx (Δ , τ) (Δ' , τ') q} {_ ,ₚ _} (uncurry Ren) (hlevel 2) λ i → keep (from-same-ren _ _ x y α i) refl-same-ren : ∀ {Γ Δ} (x : Ren Γ Δ) → ⌞ same-ren (refl-same-cx Γ) (refl-same-cx Δ) x x ⌟ refl-same-ren stop = lift tt refl-same-ren (drop x) = refl-same-ren x refl-same-ren (keep x) = refl-same-ren x same-var : ∀ {Γ' τ'} → ⌞ same-cx Γ Γ' ⌟ → ⌞ same-ty τ τ' ⌟ → Var Γ τ → Var Γ' τ' → Prop ℓ same-var q p stop stop = el! (Lift _ ⊤) same-var q p stop _ = el! (Lift _ ⊥) same-var q p (pop x) (pop y) = same-var (q .fst) p x y same-var q p (pop x) _ = el! (Lift _ ⊥) from-same-var : ∀ {Γ' τ'} (p : ⌞ same-cx Γ Γ' ⌟) (q : ⌞ same-ty τ τ' ⌟) x y → ⌞ same-var p q x y ⌟ → PathP (λ i → Var (from-same-cx Γ Γ' p i) (from-same-ty τ τ' q i)) x y from-same-var {Γ = Γ , τ} {τ = τ} {Γ' , τ'} {τ'} p q stop stop α = subst (λ e → PathP (λ i → Var (from-same-cx Γ Γ' (p .fst) i , from-same-ty τ τ' (p .snd) i) (from-same-ty τ τ' e i)) stop stop) {x = p .snd} {y = q} prop! λ i → stop from-same-var p q (pop x) (pop y) α = λ i → Var.pop (from-same-var (p .fst) q x y α i) same-ne : ∀ {Γ' τ'} → ⌞ same-cx Γ Γ' ⌟ → ⌞ same-ty τ τ' ⌟ → Ne Γ τ → Ne Γ' τ' → Prop ℓ same-nf : ∀ {Γ' τ'} → ⌞ same-cx Γ Γ' ⌟ → ⌞ same-ty τ τ' ⌟ → Nf Γ τ → Nf Γ' τ' → Prop ℓ same-nf q p unit unit = el! (Lift _ ⊤) same-nf q p unit _ = el! (Lift _ ⊥) same-nf q p (lam x) (lam y) = same-nf (q , p .fst) (p .snd) x y same-nf q p (lam x) _ = el! (Lift _ ⊥) same-nf q p (pair a b) (pair x y) = el! (⌞ same-nf q (p .fst) a x ⌟ × ⌞ same-nf q (p .snd) b y ⌟) same-nf q p (pair a b) _ = el! (Lift _ ⊥) same-nf q p (ne x) (ne y) = same-ne q p x y same-nf q p (ne x) _ = el! (Lift _ ⊥) same-ne {Γ = Γ} {τ = τ} {Γ'} {τ'} q p (var x) (var y) = same-var q p x y same-ne _ _ (var x) _ = el! (Lift _ ⊥) same-ne {Γ = Γ} q p (app {τ = τ} f x) (app {τ = σ} g y) = el! ( Σ[ r ∈ same-ty τ σ ] (⌞ same-ne q (r , p) f g ⌟ × ⌞ same-nf q r x y ⌟)) same-ne _ _ (app f x) _ = el! (Lift _ ⊥) same-ne q p (fstₙ {σ = τ} x) (fstₙ {σ = σ} y) = el! ( Σ[ r ∈ same-ty τ σ ] ⌞ same-ne q (p , r) x y ⌟) same-ne _ _ (fstₙ x) _ = el! (Lift _ ⊥) same-ne q p (sndₙ {τ = τ} x) (sndₙ {τ = σ} y) = el! ( Σ[ r ∈ same-ty τ σ ] ⌞ same-ne q (r , p) x y ⌟) same-ne _ _ (sndₙ x) _ = el! (Lift _ ⊥) same-ne q p (hom {a = τ} x a) (hom {a = σ} y b) = el! ( Σ[ r ∈ same-ty τ σ ] ( PathP (λ i → Edge (from-same-ty τ σ r i) (p i)) x y × ⌞ same-nf q r a b ⌟ )) same-ne _ _ (hom x a) _ = el! (Lift _ ⊥) from-same-ne : ∀ {Γ' τ'} (p : ⌞ same-cx Γ Γ' ⌟) (q : ⌞ same-ty τ τ' ⌟) x y → ⌞ same-ne p q x y ⌟ → PathP (λ i → Ne (from-same-cx Γ Γ' p i) (from-same-ty τ τ' q i)) x y from-same-nf : ∀ {Γ' τ'} (p : ⌞ same-cx Γ Γ' ⌟) (q : ⌞ same-ty τ τ' ⌟) x y → ⌞ same-nf p q x y ⌟ → PathP (λ i → Nf (from-same-cx Γ Γ' p i) (from-same-ty τ τ' q i)) x y from-same-ne p q (var x) (var y) α = λ i → var (from-same-var _ _ x y α i) from-same-ne p q (app f x) (app g y) α = λ i → app (from-same-ne _ _ f g (α .snd .fst) i) (from-same-nf _ _ x y (α .snd .snd) i) from-same-ne p q (fstₙ x) (fstₙ y) α = λ i → fstₙ (from-same-ne _ _ x y (α .snd) i) from-same-ne p q (sndₙ x) (sndₙ y) α = λ i → sndₙ (from-same-ne _ _ x y (α .snd) i) from-same-ne p q (hom f x) (hom g y) α = λ i → hom (α .snd .fst i) (from-same-nf _ _ x y (α .snd .snd) i) from-same-nf p q (lam x) (lam y) α = λ i → lam (from-same-nf _ _ x y α i) from-same-nf p q (pair a b) (pair x y) α = λ i → pair (from-same-nf _ _ a x (α .fst) i) (from-same-nf _ _ b y (α .snd) i) from-same-nf p q unit unit α = λ i → unit from-same-nf p q (ne x) (ne y) α = λ i → ne (from-same-ne p q x y α i) refl-same-var : (v : Var Γ τ) → ⌞ same-var (refl-same-cx Γ) (refl-same-ty τ) v v ⌟ refl-same-var stop = lift tt refl-same-var (pop v) = refl-same-var v refl-same-ne : (v : Ne Γ τ) → ⌞ same-ne (refl-same-cx Γ) (refl-same-ty τ) v v ⌟ refl-same-nf : (v : Nf Γ τ) → ⌞ same-nf (refl-same-cx Γ) (refl-same-ty τ) v v ⌟ refl-same-ne (var x) = refl-same-var x refl-same-ne (app {τ = τ} v x) = refl-same-ty τ , refl-same-ne v , refl-same-nf x refl-same-ne (fstₙ {σ = σ} v) = refl-same-ty σ , refl-same-ne v refl-same-ne (sndₙ {τ = τ} v) = refl-same-ty τ , refl-same-ne v refl-same-ne (hom {a = τ} x v) = refl-same-ty τ , is-set→cast-pathp {p = refl} {q = from-same-ty τ τ (refl-same-ty τ)} (λ e → Edge e _) (hlevel 2) refl , refl-same-nf v refl-same-nf (lam x) = refl-same-nf x refl-same-nf (pair a b) = refl-same-nf a , refl-same-nf b refl-same-nf unit = lift tt refl-same-nf (ne x) = refl-same-ne x from-same-var' : (x y : Var Γ τ) → ⌞ same-var (refl-same-cx Γ) (refl-same-ty τ) x y ⌟ → x ≡ y from-same-var' {Γ} {τ} x y p = is-set→cast-pathp {x = Γ , τ} {y = Γ , τ} {_ ,ₚ _} {refl} (uncurry Var) (hlevel 2) (from-same-var (refl-same-cx Γ) (refl-same-ty τ) x y p) from-same-nf' : (x y : Nf Γ τ) → ⌞ same-nf (refl-same-cx Γ) (refl-same-ty τ) x y ⌟ → x ≡ y from-same-nf' {Γ} {τ} x y p = is-set→cast-pathp {x = Γ , τ} {y = Γ , τ} {_ ,ₚ _} {refl} (uncurry Nf) (hlevel 2) (from-same-nf (refl-same-cx Γ) (refl-same-ty τ) x y p) from-same-ne' : (x y : Ne Γ τ) → ⌞ same-ne (refl-same-cx Γ) (refl-same-ty τ) x y ⌟ → x ≡ y from-same-ne' {Γ} {τ} x y p = is-set→cast-pathp {x = Γ , τ} {y = Γ , τ} {_ ,ₚ _} {refl} (uncurry Ne) (hlevel 2) (from-same-ne (refl-same-cx Γ) (refl-same-ty τ) x y p) instance H-Level-Ren : ∀ {n} → H-Level (Ren Γ Δ) (2 + n) H-Level-Ren {Γ} {Δ} = basic-instance 2 $ set-identity-system→hlevel (λ x y → ⌞ same-ren (refl-same-cx Γ) (refl-same-cx Δ) x y ⌟) refl-same-ren (λ x y → hlevel 1) (λ x y p → is-set→cast-pathp {x = Γ , Δ} {y = Γ , Δ} {_ ,ₚ _} {refl} (uncurry Ren) (hlevel 2) (from-same-ren (refl-same-cx Γ) (refl-same-cx Δ) x y p)) H-Level-Var : ∀ {n} → H-Level (Var Γ τ) (2 + n) H-Level-Var {Γ} {τ} = basic-instance 2 $ set-identity-system→hlevel (λ x y → ⌞ same-var (refl-same-cx Γ) (refl-same-ty τ) x y ⌟) refl-same-var (λ x y → hlevel 1) from-same-var' H-Level-Nf : ∀ {n} → H-Level (Nf Γ τ) (2 + n) H-Level-Nf {Γ} {τ} = basic-instance 2 $ set-identity-system→hlevel (λ x y → ⌞ same-nf (refl-same-cx Γ) (refl-same-ty τ) x y ⌟) refl-same-nf (λ x y → hlevel 1) from-same-nf' H-Level-Ne : ∀ {n} → H-Level (Ne Γ τ) (2 + n) H-Level-Ne {Γ} {τ} = basic-instance 2 $ set-identity-system→hlevel (λ x y → ⌞ same-ne (refl-same-cx Γ) (refl-same-ty τ) x y ⌟) refl-same-ne (λ x y → hlevel 1) from-same-ne' module _ ⦃ _ : Discrete Node ⦄ ⦃ _ : ∀ {x y} → Discrete (Edge x y) ⦄ where private dec-same-ty : ∀ τ σ → Dec ⌞ same-ty τ σ ⌟ dec-same-ty `⊤ σ with σ ... | `⊤ = yes (lift tt) ... | ` x = no λ () ... | x `× y = no λ () ... | x `⇒ y = no λ () dec-same-ty (` x) σ with σ ... | ` y = auto ... | `⊤ = no λ () ... | x `× y = no λ () ... | x `⇒ y = no λ () dec-same-ty (a `× b) σ with σ ... | x `× y = Dec-× ⦃ dec-same-ty a x ⦄ ⦃ dec-same-ty b y ⦄ ... | `⊤ = no λ () ... | ` y = no λ () ... | x `⇒ y = no λ () dec-same-ty (a `⇒ b) σ with σ ... | x `⇒ y = Dec-× ⦃ dec-same-ty a x ⦄ ⦃ dec-same-ty b y ⦄ ... | `⊤ = no λ () ... | ` y = no λ () ... | x `× y = no λ () dec-same-var : ∀ {Γ τ Γ' τ'} → (α : ⌞ same-cx Γ Γ' ⌟) → (β : ⌞ same-ty τ τ' ⌟) → (x : Var Γ τ) (y : Var Γ' τ') → Dec ⌞ same-var α β x y ⌟ dec-same-var α β stop y with y ... | stop = yes (lift tt) ... | pop _ = no λ () dec-same-var α β (pop x) y with y ... | pop y = dec-same-var (α .fst) β x y ... | stop = no λ () dec-same-ne : ∀ {Γ τ Γ' τ'} → (α : ⌞ same-cx Γ Γ' ⌟) → (β : ⌞ same-ty τ τ' ⌟) → (x : Ne Γ τ) (y : Ne Γ' τ') → Dec ⌞ same-ne α β x y ⌟ dec-same-nf : ∀ {Γ τ Γ' τ'} → (α : ⌞ same-cx Γ Γ' ⌟) → (β : ⌞ same-ty τ τ' ⌟) → (x : Nf Γ τ) (y : Nf Γ' τ') → Dec ⌞ same-nf α β x y ⌟ dec-same-nf α β (lam x) (lam y) = dec-same-nf (α , β .fst) (β .snd) x y dec-same-nf α β (pair a b) (pair x y) = Dec-× ⦃ dec-same-nf α (β .fst) a x ⦄ ⦃ dec-same-nf α (β .snd) b y ⦄ dec-same-nf α β unit unit = yes (lift tt) dec-same-nf α β (ne x) (ne y) = dec-same-ne α β x y dec-same-ne α β (var x) y with y ... | var y = dec-same-var α β x y ... | app _ _ = no λ () ... | fstₙ _ = no λ () ... | sndₙ _ = no λ () ... | hom _ _ = no λ () dec-same-ne α β (app {τ = τ} f x) y with y ... | app {τ = σ} g y = Dec-Σ (hlevel 1) (dec-same-ty τ σ) λ γ → Dec-× ⦃ dec-same-ne α (γ , β) f g ⦄ ⦃ dec-same-nf α γ x y ⦄ ... | var _ = no λ () ... | fstₙ _ = no λ () ... | sndₙ _ = no λ () ... | hom _ _ = no λ () dec-same-ne α β (hom {a = a} f x) y with y ... | hom {a = b} g y = Dec-Σ (hlevel 1) (dec-same-ty a b) λ γ → Dec-× ⦃ invmap to-pathp from-pathp auto ⦄ ⦃ dec-same-nf α γ x y ⦄ ... | var _ = no λ () ... | app _ _ = no λ () ... | fstₙ _ = no λ () ... | sndₙ _ = no λ () dec-same-ne α β (fstₙ {σ = τ} x) y with y ... | fstₙ {σ = σ} y = Dec-Σ (hlevel 1) (dec-same-ty τ σ) λ γ → dec-same-ne α (β , γ) x y ... | var _ = no λ () ... | app _ _ = no λ () ... | sndₙ _ = no λ () ... | hom _ _ = no λ () dec-same-ne α β (sndₙ {τ = τ} x) y with y ... | sndₙ {τ = σ} y = Dec-Σ (hlevel 1) (dec-same-ty τ σ) λ γ → dec-same-ne α (γ , β) x y ... | var _ = no λ () ... | app _ _ = no λ () ... | fstₙ _ = no λ () ... | hom _ _ = no λ () instance Discrete-Ty : Discrete Ty Discrete-Ty .decide x y = invmap (from-same-ty x y) (λ p → subst (λ e → ⌞ same-ty x e ⌟) p (refl-same-ty x)) (dec-same-ty x y) Discrete-Var : ∀ {Γ τ} → Discrete (Var Γ τ) Discrete-Var {Γ} {τ} .decide x y = invmap (from-same-var' x y) (λ p → subst (λ e → ⌞ same-var (refl-same-cx Γ) (refl-same-ty τ) x e ⌟) p (refl-same-var x)) (dec-same-var (refl-same-cx Γ) (refl-same-ty τ) x y) Discrete-Ne : ∀ {Γ τ} → Discrete (Ne Γ τ) Discrete-Ne {Γ} {τ} .decide x y = invmap (from-same-ne' x y) (λ p → subst (λ e → ⌞ same-ne (refl-same-cx Γ) (refl-same-ty τ) x e ⌟) p (refl-same-ne x)) (dec-same-ne (refl-same-cx Γ) (refl-same-ty τ) x y) Discrete-Nf : ∀ {Γ τ} → Discrete (Nf Γ τ) Discrete-Nf {Γ} {τ} .decide x y = invmap (from-same-nf' x y) (λ p → subst (λ e → ⌞ same-nf (refl-same-cx Γ) (refl-same-ty τ) x e ⌟) p (refl-same-nf x)) (dec-same-nf (refl-same-cx Γ) (refl-same-ty τ) x y)
Of course, we also have embeddings from variables, neutrals and
normals back into the syntax, which we will continue to write using
decorated “semantic brackets” ⟦_⟧
.
⟦_⟧ⁿ : Var Γ τ → Mor ⟦ Γ ⟧ᶜ τ
⟦_⟧ₙ : Nf Γ τ → Mor ⟦ Γ ⟧ᶜ τ
⟦_⟧ₛ : Ne Γ τ → Mor ⟦ Γ ⟧ᶜ τ
Due to a paucity of letters and unicode sub/superscripts, we went with
superscript ⟦_⟧ⁿ
for “name” interpreting
variables, and subscript ⟦_⟧ₛ
for “stuck” interpreting
neutrals.
⟦_⟧
.⟦ stop ⟧ⁿ = `π₂ ⟦ pop x ⟧ⁿ = ⟦ x ⟧ⁿ `∘ `π₁ ⟦ lam h ⟧ₙ = `ƛ ⟦ h ⟧ₙ ⟦ pair a b ⟧ₙ = ⟦ a ⟧ₙ `, ⟦ b ⟧ₙ ⟦ ne x ⟧ₙ = ⟦ x ⟧ₛ ⟦ unit ⟧ₙ = `! ⟦ var x ⟧ₛ = ⟦ x ⟧ⁿ ⟦ app f x ⟧ₛ = `ev `∘ (⟦ f ⟧ₛ `, ⟦ x ⟧ₙ) ⟦ fstₙ h ⟧ₛ = `π₁ `∘ ⟦ h ⟧ₛ ⟦ sndₙ h ⟧ₛ = `π₂ `∘ ⟦ h ⟧ₛ ⟦ hom s h ⟧ₛ = (` s) `∘ ⟦ h ⟧ₙ
Next, we equip renamings with the structure of a category; variables,
neutrals and normals get the structure of presheaves over the category
of renamings.
_∘ʳ_ : ∀ {Γ Δ Θ} → Ren Γ Δ → Ren Δ Θ → Ren Γ Θ
ren-var : ∀ {Γ Δ τ} → Ren Γ Δ → Var Δ τ → Var Γ τ
ren-ne : ∀ {Γ Δ τ} → Ren Δ Γ → Ne Γ τ → Ne Δ τ
ren-nf : ∀ {Γ Δ τ} → Ren Δ Γ → Nf Γ τ → Nf Δ τ
As is becoming traditional, the implementation is hidden in this <details>
block, and the verification of the myriad equational laws is omitted
from the page entirely. Many cases were bashed in the making of this
module.
stop ∘ʳ ρ = ρ drop σ ∘ʳ ρ = drop (σ ∘ʳ ρ) keep σ ∘ʳ stop = keep σ keep σ ∘ʳ drop ρ = drop (σ ∘ʳ ρ) keep σ ∘ʳ keep ρ = keep (σ ∘ʳ ρ) ren-var stop v = v ren-var (drop σ) v = pop (ren-var σ v) ren-var (keep σ) stop = stop ren-var (keep σ) (pop v) = pop (ren-var σ v) ren-ne σ (var v) = var (ren-var σ v) ren-ne σ (app f a) = app (ren-ne σ f) (ren-nf σ a) ren-ne σ (fstₙ a) = fstₙ (ren-ne σ a) ren-ne σ (sndₙ a) = sndₙ (ren-ne σ a) ren-ne σ (hom s a) = hom s (ren-nf σ a) ren-nf σ (lam n) = lam (ren-nf (keep σ) n) ren-nf σ (pair a b) = pair (ren-nf σ a) (ren-nf σ b) ren-nf σ (ne x) = ne (ren-ne σ x) ren-nf σ unit = unit
∘ʳ-idr : (ρ : Ren Δ Θ) → ρ ∘ʳ stop ≡ ρ ∘ʳ-idr stop = refl ∘ʳ-idr (drop ρ) = ap drop (∘ʳ-idr ρ) ∘ʳ-idr (keep ρ) = refl ∘ʳ-assoc : ∀ {w x y z} (f : Ren y z) (g : Ren x y) (h : Ren w x) → ((h ∘ʳ g) ∘ʳ f) ≡ (h ∘ʳ (g ∘ʳ f)) ∘ʳ-assoc f g stop = refl ∘ʳ-assoc f g (drop h) = ap drop (∘ʳ-assoc f g h) ∘ʳ-assoc f stop (keep h) = refl ∘ʳ-assoc f (drop g) (keep h) = ap drop (∘ʳ-assoc f g h) ∘ʳ-assoc stop (keep g) (keep h) = refl ∘ʳ-assoc (drop f) (keep g) (keep h) = ap drop (∘ʳ-assoc f g h) ∘ʳ-assoc (keep f) (keep g) (keep h) = ap keep (∘ʳ-assoc f g h) ren-⟦⟧ⁿ : (ρ : Ren Δ Γ) (v : Var Γ τ) → ⟦ ren-var ρ v ⟧ⁿ ≡ ⟦ v ⟧ⁿ `∘ ⟦ ρ ⟧ʳ ren-⟦⟧ₛ : (ρ : Ren Δ Γ) (t : Ne Γ τ) → ⟦ ren-ne ρ t ⟧ₛ ≡ ⟦ t ⟧ₛ `∘ ⟦ ρ ⟧ʳ ren-⟦⟧ₙ : (ρ : Ren Δ Γ) (t : Nf Γ τ) → ⟦ ren-nf ρ t ⟧ₙ ≡ ⟦ t ⟧ₙ `∘ ⟦ ρ ⟧ʳ ren-⟦⟧ⁿ stop v = Syn.intror refl ren-⟦⟧ⁿ (drop ρ) v = Syn.pushl (ren-⟦⟧ⁿ ρ v) ren-⟦⟧ⁿ (keep ρ) stop = sym (Syn.π₂∘⟨⟩) -- sym (Syn.π₂∘⟨⟩ ∙ Syn.idl _) ren-⟦⟧ⁿ (keep ρ) (pop v) = Syn.pushl (ren-⟦⟧ⁿ ρ v) ∙ sym (Syn.pullr Syn.π₁∘⟨⟩) ren-⟦⟧ₛ ρ (var x) = ren-⟦⟧ⁿ ρ x ren-⟦⟧ₛ ρ (app f x) = ap₂ _`∘_ refl (ap₂ _`,_ (ren-⟦⟧ₛ ρ f) (ren-⟦⟧ₙ ρ x) ∙ sym (Syn.⟨⟩∘ _)) ∙ Syn.pulll refl ren-⟦⟧ₛ ρ (fstₙ t) = Syn.pushr (ren-⟦⟧ₛ ρ t) ren-⟦⟧ₛ ρ (sndₙ t) = Syn.pushr (ren-⟦⟧ₛ ρ t) ren-⟦⟧ₛ ρ (hom x a) = Syn.pushr (ren-⟦⟧ₙ ρ a) ren-⟦⟧ₙ ρ (lam t) = ap `ƛ (ren-⟦⟧ₙ (keep ρ) t) ∙ sym (Cartesian-closed.unique Free-closed _ (ap₂ _`∘_ refl rem₁ ∙ Syn.pulll `ƛβ ∙ ap₂ _`∘_ refl (ap₂ _`,_ refl `idl))) where rem₁ : (⟦ lam t ⟧ₙ `∘ ⟦ ρ ⟧ʳ) Syn.⊗₁ `id ≡ (⟦ lam t ⟧ₙ Syn.⊗₁ `id) `∘ ⟦ ρ ⟧ʳ Syn.⊗₁ `id rem₁ = Bifunctor.first∘first Syn.×-functor ren-⟦⟧ₙ ρ (pair a b) = ap₂ _`,_ (ren-⟦⟧ₙ ρ a) (ren-⟦⟧ₙ ρ b) ∙ sym (Syn.⟨⟩∘ _) ren-⟦⟧ₙ ρ (ne x) = ren-⟦⟧ₛ ρ x ren-⟦⟧ₙ ρ unit = `!-η _ ren-⟦⟧ʳ : ∀ {Γ Δ Θ} (f : Ren Γ Δ) (g : Ren Δ Θ) → ⟦ f ∘ʳ g ⟧ʳ ≡ ⟦ g ⟧ʳ `∘ ⟦ f ⟧ʳ ren-⟦⟧ʳ stop g = sym `idr ren-⟦⟧ʳ (drop f) g = Syn.pushl (ren-⟦⟧ʳ f g) ren-⟦⟧ʳ (keep f) stop = sym `idl ren-⟦⟧ʳ (keep f) (drop g) = Syn.pushl (ren-⟦⟧ʳ f g) ∙ sym (Syn.pullr `π₁β) ren-⟦⟧ʳ (keep f) (keep g) = sym (Syn.⟨⟩-unique (Syn.pulll `π₁β ∙ Syn.pullr `π₁β ∙ Syn.pulll (sym (ren-⟦⟧ʳ f g))) (Syn.pulll `π₂β ∙ `π₂β)) ren-var-∘ʳ : ∀ {Γ Δ Θ} (ρ : Ren Γ Δ) (σ : Ren Δ Θ) (x : Var Θ τ) → ren-var (ρ ∘ʳ σ) x ≡ ren-var ρ (ren-var σ x) ren-var-∘ʳ ρ stop x = ap (λ e → ren-var e x) (∘ʳ-idr ρ) ren-var-∘ʳ stop (drop σ) x = refl ren-var-∘ʳ (drop ρ) (drop σ) x = ap pop (ren-var-∘ʳ ρ (drop σ) x) ren-var-∘ʳ (keep ρ) (drop σ) x = ap pop (ren-var-∘ʳ ρ σ x) ren-var-∘ʳ stop (keep σ) x = refl ren-var-∘ʳ (drop ρ) (keep σ) x = ap pop (ren-var-∘ʳ ρ (keep σ) x) ren-var-∘ʳ (keep ρ) (keep σ) stop = refl ren-var-∘ʳ (keep ρ) (keep σ) (pop x) = ap pop (ren-var-∘ʳ ρ σ x) ren-nf-∘ʳ : ∀ {Γ Δ Θ} (ρ : Ren Γ Δ) (σ : Ren Δ Θ) (x : Nf Θ τ) → ren-nf (ρ ∘ʳ σ) x ≡ ren-nf ρ (ren-nf σ x) ren-ne-∘ʳ : ∀ {Γ Δ Θ} (ρ : Ren Γ Δ) (σ : Ren Δ Θ) (x : Ne Θ τ) → ren-ne (ρ ∘ʳ σ) x ≡ ren-ne ρ (ren-ne σ x) ren-nf-∘ʳ ρ σ (lam x) = ap lam (ren-nf-∘ʳ (keep ρ) (keep σ) x) ren-nf-∘ʳ ρ σ (pair a b) = ap₂ pair (ren-nf-∘ʳ ρ σ a) (ren-nf-∘ʳ ρ σ b) ren-nf-∘ʳ ρ σ unit = refl ren-nf-∘ʳ ρ σ (ne x) = ap ne (ren-ne-∘ʳ ρ σ x) ren-ne-∘ʳ ρ σ (var x) = ap var (ren-var-∘ʳ ρ σ x) ren-ne-∘ʳ ρ σ (app f x) = ap₂ app (ren-ne-∘ʳ ρ σ f) (ren-nf-∘ʳ ρ σ x) ren-ne-∘ʳ ρ σ (fstₙ x) = ap fstₙ (ren-ne-∘ʳ ρ σ x) ren-ne-∘ʳ ρ σ (sndₙ x) = ap sndₙ (ren-ne-∘ʳ ρ σ x) ren-ne-∘ʳ ρ σ (hom n x) = ap (hom n) (ren-nf-∘ʳ ρ σ x) data Keep : Cx → Cx → Type ℓ where stop : Keep Γ Γ keep : Keep Γ Δ → Keep (Γ , τ) (Δ , τ) keep→ren : Keep Γ Δ → Ren Γ Δ keep→ren stop = stop keep→ren (keep x) = keep (keep→ren x) keep→ctx : Keep Γ Δ → Δ ≡ Γ keep→ctx stop = refl keep→ctx (keep x) = ap₂ Cx._,_ (keep→ctx x) refl ren-var-stop : (ρ : Keep Γ Δ) (x : Var Δ τ) → PathP (λ i → Var (keep→ctx ρ (~ i)) τ) (ren-var (keep→ren ρ) x) x ren-var-stop stop x = refl ren-var-stop (keep ρ) stop i = stop ren-var-stop (keep ρ) (pop x) i = pop (ren-var-stop ρ x i) ren-nf-stop : (ρ : Keep Γ Γ) (x : Nf Γ τ) → ren-nf (keep→ren ρ) x ≡ x ren-ne-stop : (ρ : Keep Γ Γ) (x : Ne Γ τ) → ren-ne (keep→ren ρ) x ≡ x ren-nf-stop ρ (lam x) = ap lam (ren-nf-stop (keep ρ) x) ren-nf-stop ρ (pair a b) = ap₂ pair (ren-nf-stop ρ a) (ren-nf-stop ρ b) ren-nf-stop ρ unit = refl ren-nf-stop ρ (ne x) = ap ne (ren-ne-stop ρ x) ren-ne-stop {τ = τ} ρ (var x) = ap var ( from-pathp⁻ (ren-var-stop ρ x) ∙∙ ap (λ e → subst (λ x → Var x τ) e x) {x = keep→ctx ρ} {y = refl} prop! ∙∙ transport-refl _ ) ren-ne-stop ρ (app f x) = ap₂ app (ren-ne-stop ρ f) (ren-nf-stop ρ x) ren-ne-stop ρ (fstₙ x) = ap fstₙ (ren-ne-stop ρ x) ren-ne-stop ρ (sndₙ x) = ap sndₙ (ren-ne-stop ρ x) ren-ne-stop ρ (hom x n) = ap (hom x) (ren-nf-stop ρ n)
Finally, one billion copattern matches make all of this nonsense into actual categorical objects.
Rens : Precategory ℓ ℓ Rens .Precategory.Ob = Cx Rens .Precategory.Hom = Ren Rens .Precategory.Hom-set X Y = hlevel 2 Rens .Precategory.id = stop Rens .Precategory._∘_ f g = g ∘ʳ f Rens .Precategory.idr f = refl Rens .Precategory.idl f = ∘ʳ-idr f Rens .Precategory.assoc f g h = ∘ʳ-assoc f g h Sem : Type _ Sem = ⌞ PSh ℓ Rens ⌟ Ren↪Ctx : Functor Rens Free-ccc Ren↪Ctx .F₀ = ⟦_⟧ᶜ Ren↪Ctx .F₁ = ⟦_⟧ʳ Ren↪Ctx .F-id = refl Ren↪Ctx .F-∘ f g = ren-⟦⟧ʳ g f Tm : Functor Free-ccc (PSh ℓ Rens) Tm = Nerve Ren↪Ctx
open Pl ℓ Rens open Pe Rens private module Ren↪Ctx = Functor Ren↪Ctx module Sem = Cartesian-category {C = PSh ℓ Rens} PSh-cartesian module Tm = Functor Tm
Nfs : Ty → Sem Nfs τ .F₀ Γ = el! (Nf Γ τ) Nfs τ .F₁ ρ = ren-nf ρ Nfs τ .F-id = ext (ren-nf-stop stop) Nfs τ .F-∘ ρ σ = ext (ren-nf-∘ʳ ρ σ) rnf : Nfs τ => Tm.₀ τ rnf .η Γ σ = ⟦ σ ⟧ₙ rnf .is-natural x y f = ext (λ s → ren-⟦⟧ₙ f s) Nes : Ty → Sem Nes τ .F₀ Γ = el! (Ne Γ τ) Nes τ .F₁ ρ = ren-ne ρ Nes τ .F-id = ext (ren-ne-stop stop) Nes τ .F-∘ ρ σ = ext (ren-ne-∘ʳ ρ σ) rne : Nes τ => Tm.₀ τ rne .η Γ σ = ⟦ σ ⟧ₛ rne .is-natural x y f = ext (λ s → ren-⟦⟧ₛ f s)
Back from the case-bash trenches, the nerve
along the embedding
is a functor into presheaves over renamings which we write
and being a composite of Cartesian functors,
is also Cartesian. It fails to be Cartesian closed, though, so
we can not appeal to initiality in the same way. Moreover, just applying
the initiality trick again wouldn’t bring our Nf
into the picture, either.
Even worse, that’s not how we stated the universal property of
!
open Cartesian-functor using (pres-products ; pres-terminal) Tm-cartesian : Cartesian-functor Tm Free-cartesian PSh-cartesian Tm-cartesian .pres-products a b = Sem.make-invertible (NT (elim! (λ a p q → p `, q)) λ x y f → ext λ p q → sym (Syn.⟨⟩∘ _)) (ext (λ i a b → `π₁β ,ₚ `π₂β)) (ext (λ i x → sym `πη)) Tm-cartesian .pres-terminal x .centre = NT (λ _ _ → `!) (λ x y f → ext λ a → `!-η _) Tm-cartesian .pres-terminal x .paths a = ext λ i x → `!-η _
However, there is an off-the-shelf solution we can reach for: since is properly Cartesian closed (it has pullbacks), the Artin gluing is a Cartesian closed category over the syntax, as required by the universal property.
open Cat.Displayed.Instances.Gluing PSh-cartesian Free-cartesian Tm-cartesian
Surprisingly though, due to our definition of having base terms with arbitrary simple types as their domain, we’re not quite done putting together the induction. We would like to define the model of in to have the base types interpreted by normal forms (equivalently neutrals), but we have to know ahead of time how to turn our interpretation of the other simple types into neutrals.
Normalisation algebras🔗
To actually get normal forms out the other side, we will equip the
objects of
over simple types
with a structure we refer to as a normalisation
algebra. Recalling that an object in
pairs a presheaf
with a natural transformation
a normalisation algebra structure consists of further natural
transformations reifies
from
and reflects
from
5
record Nfa {τ : Ty} (P : Gl ʻ τ) : Type ℓ where field reifies : P .dom => Nfs τ reflects : Nes τ => P .dom
⟦_⟧ₚ : ∀ {Γ} → P .dom ʻ Γ → Mor ⟦ Γ ⟧ᶜ τ ⟦_⟧ₚ {Γ} x = P .map .η Γ x reify : ∀ {Γ} → P .dom ʻ Γ → Nf Γ τ reify = reifies .η _ reflect : ∀ {Γ} → Ne Γ τ → P .dom ʻ Γ reflect = reflects .η _ field
These are required to make the following triangles commute.
com₀ : {Γ : Cx} (n : Ne Γ τ) → ⟦ reflect n ⟧ₚ ≡ ⟦ n ⟧ₛ com₁ : {Γ : Cx} (x : P .dom ʻ Γ) → ⟦ x ⟧ₚ ≡ ⟦ reify x ⟧ₙ
reifyₙ : ∀ {Γ Δ} {ρ : Ren Γ Δ} {x : P .dom ʻ Δ} → reify (P .dom ⟪ ρ ⟫ x) ≡ ren-nf ρ (reify x) reifyₙ {Γ} {Δ} {ρ} {x} = reifies .is-natural Δ Γ ρ $ₚ x reflectₙ : ∀ {Γ Δ} {ρ : Ren Γ Δ} {x : Ne Δ τ} → reflect (ren-ne ρ x) ≡ P .dom ⟪ ρ ⟫ (reflect x) reflectₙ {Γ} {Δ} {ρ} {x} = reflects .is-natural Δ Γ ρ $ₚ x GlTm-closed = Gl-closed PSh-closed Free-closed PSh-pullbacks open Cartesian-closed-over Gl Gl-cartesian {Free-closed} GlTm-closed using ([_,_]') open Nfa
We start by building a normalisation algebra on the presheaf of
normals, which we take as the model of base types in
This is only slightly worse than trivial, because building the
reflect
map —
embedding neutrals into normals — is not literally the identity
function, but an inductive constructor instead.
Gl-base : (n : Node) → Gl ʻ (` n) Gl-base x = cut (rnf {` x}) base-nfa : ∀ {x} → Nfa (Gl-base x) base-nfa .reifies = idnt base-nfa .reflects = record where η Γ x = ne x is-natural Γ Δ ρ = ext λ a → refl base-nfa .com₀ x = refl base-nfa .com₁ x = refl
Pairs🔗
prod-nfa : ∀ {τ σ x y} → Nfa {τ} x → Nfa {σ} y → Nfa (x ×Gl y) prod-nfa xnf ynf = record where module x = Nfa xnf module y = Nfa ynf
Next, we tackle products in
Assume we already have normalisation algebras on
and
We can reify a pair into the pair
normal form, reifying each
component in turn, and this pairing preserves naturality.
reifies = record { η = λ Γ (a , b) → pair (x.reify a) (y.reify b) ; is-natural = λ Γ Δ ρ → ext (λ a b → ap₂ pair x.reifyₙ y.reifyₙ) }
Conversely, we reflect a neutral into a pair by reflecting each
projection, first wrapping them in the constructors fstₙ
and sndₙ
to get something in the
right type. Again, these constructions preserve naturality.
reflects = Sem.⟨ NT (λ Γ x → x.reflect (fstₙ x)) (λ x y f → ext λ a → x.reflectₙ) , NT (λ Γ x → y.reflect (sndₙ x)) (λ x y f → ext λ a → y.reflectₙ) ⟩
Finally, we must commute those triangles. One is a straightforward calculation and the other is a trivial invocation of the corresponding triangle for the normalisation algebras we assumed.
com₀ x = x.⟦ x.reflect (fstₙ x) ⟧ₚ `, y.⟦ y.reflect (sndₙ x) ⟧ₚ ≡⟨ ap₂ _`,_ (x.com₀ _) (y.com₀ _) ⟩≡ `π₁ `∘ ⟦ x ⟧ₛ `, `π₂ `∘ ⟦ x ⟧ₛ ≡˘⟨ Syn.⟨⟩∘ _ ⟩≡˘ (`π₁ `, `π₂) `∘ ⟦ x ⟧ₛ ≡⟨ Syn.eliml (ap₂ _`,_ (sym `idr) (sym `idr) ∙ sym `πη) ⟩≡ ⟦ x ⟧ₛ ∎ com₁ (a , b) = ap₂ _`,_ (x.com₁ _) (y.com₁ _)
Functions🔗
arrow-nfa : ∀ {τ σ x y} → Nfa {τ} x → Nfa {σ} y → Nfa [ x , y ]' arrow-nfa {x = x} {y = y} xnf ynf = arr where module x = Nfa xnf module y = Nfa ynf
Next come exponential objects in which are trickier but still possible in full generality. We will once again assume that we have already built normalisation algebras for the domain and codomain We start with the implementation of reification.
Recall that the exponentials in are given by a certain pullback of and so that (at context they are given by a pair of an expression and a natural transformation with components saying that we can get a semantic value in from a renaming expressing as an extension of and a semantic value in the domain At this point, we do not yet know how to normalise the expression to obtain a normal form, so our hope lies with the natural transformation
We know that a normal form in
must be of the form
for a unique
so that we may reduce to finding a suitable
and semantic value
and, given that we can reflect
semantic values into
we can use the neutral
To summarize, the definition of the presheaf exponential over renaming
gives an evaluation function that is stable under (in particular)
weakening. We can thus reify a natural transformation by applying it to
a fresh variable, reifying the result, and abstracting over
that variable, getting a normal form in the right context.
arr : Nfa [ x , y ]' arr .reifies .η Γ (_ , f , p) = lam $ y.reify $ f .η _ (drop stop , x.reflect (var stop))
Reflection takes much less explanation. Given a neutral we can directly take as the first component of the glued exponential, since that is the operation we are defining. To implement the evaluation natural transformation, we see that it suffices to, given an extended context through a renaming and a semantic value produce a neutral since that reflects into a semantic value
To this end, we can rename our neutral function
to obtain
and the app
constructor says we can
apply neutral functions to normal arguments, so we obtain
A simple calculation shows that this “evaluation” map is natural, and
that it is a definable operation built from
so we are done with the construction.
arr .reflects .η Γ x .fst = ⟦ x ⟧ₛ arr .reflects .η Γ x .snd .fst = record where η Δ (ρ , s) = y.reflect (app (ren-ne ρ x) (x.reify s)) is-natural Δ Θ ρ = ext λ σ s → ap y.reflect (ap₂ Ne.app (ren-ne-∘ʳ ρ σ x) x.reifyₙ) ∙ y.reflectₙ arr .reflects .η Γ x .snd .snd = ext λ Γ ρ s → sym $ y.⟦ y.reflect (app (ren-ne ρ x) (x.reify s)) ⟧ₚ ≡⟨ y.com₀ _ ⟩≡ `ev `∘ (⟦ ren-ne ρ x ⟧ₛ `, ⟦ x.reify s ⟧ₙ) ≡⟨ ap₂ (λ a b → `ev `∘ (a `, b)) (ren-⟦⟧ₛ ρ x) (sym (x.com₁ _)) ⟩≡ `ev `∘ (⟦ x ⟧ₛ `∘ ⟦ ρ ⟧ʳ `, x.⟦ s ⟧ₚ) ∎
The two triangles commute by a short calculation — the coherence
involving reflect
is definitional, while
the one involving reify
uses the pullback
condition, the “definability” of the natural transformation, to reduce
it to something we can do substitution algebra on.
arr .com₀ f = refl arr .com₁ (φ , f , α) = sym $ `ƛ ⟦ y.reify (f .η _ (drop stop , x.reflect (var stop))) ⟧ₙ ≡⟨ ap `ƛ (sym (y.com₁ _) ∙ sym (unext α _ _ _)) ⟩≡ `ƛ (`ev `∘ (φ `∘ `id `∘ `π₁ `, x.⟦ x.reflect (var stop) ⟧ₚ)) ≡⟨ ap `ƛ (ap₂ (λ a b → `ev `∘ (a `, b)) (ap (φ `∘_) `idl) (x.com₀ _ ∙ sym `idl)) ⟩≡ `ƛ (`ev `∘ (φ `∘ `π₁ `, `id `∘ `π₂)) ≡⟨ sym `ƛη ⟩≡ φ ∎
Strictly speaking, we must still show that the reification and reflection functions we defined above are natural. Since these are all defined in terms of other natural operations, the verification is straightforward, and so we omit it.
arr .reifies .is-natural Γ Δ ρ = ext λ x y p → ap Nf.lam (ap y.reify (ap (λ e → y .η _ e) (ap drop (sym (∘ʳ-idr ρ)) ,ₚ x.reflectₙ) ∙ (y .is-natural (Γ , _) (Δ , _) (keep ρ) ·ₚ (drop stop , _))) ∙ y.reifyₙ) arr .reflects .is-natural Γ Δ ρ = ext λ n → Σ-pathp (ren-⟦⟧ₛ ρ n) $ Σ-prop-pathp! (ext λ Θ σ s → ap y.reflect (ap₂ app (sym (ren-ne-∘ʳ σ ρ n)) refl))
Normalisation by evaluation🔗
We’re finally ready to construct a section of First, we build a normalisation algebra on the denotation of every simple type, by a straightforward recursive argument using the “methods” implemented above — the case for the unit type is too simple to get a name.
normalisation : ∀ τ → Nfa {τ} (Ty-nf τ) normalisation (` x) = base-nfa normalisation (τ `× σ) = prod-nfa (normalisation τ) (normalisation σ) normalisation (τ `⇒ σ) = arrow-nfa (normalisation τ) (normalisation σ) normalisation `⊤ = record { reifies = NT (λ Γ _ → unit) (λ Γ Δ ρ → refl) ; reflects = Sem.! ; com₀ = λ _ → `!-η _ ; com₁ = λ _ → refl }
This lets us model the base terms in
which is possible because (by construction) every base term
induces a natural transformation
which is componentwise the constructor hom
, and we’ve constructed
natural transformations
from the interpretation of a simple type into normals, namely reifies
.
private terms : ∀ {x : Ty} {y : Node} (e : Edge x y) → Gl.Hom[ (` e) ] (Ty-nf x) (Gl-base y) terms {x = x} {y = y} e .map = NT (λ Γ x → ne (hom e x)) (λ x y f → refl) ∘nt Nfa.reifies (normalisation x) terms {x = x} {y = y} e .com = ext λ Γ ρ → ap ((` e) `∘_) (sym (Nfa.com₁ (normalisation x) _))
We thus have a section of Gl
, which performs Normalisation
.
Normalisation : Section Gl Normalisation = Free-ccc-elim terms
Let’s see how to use this to actually normalise an element
of Mor
. First,
the section of Gl
turns
into a natural transformation
To get an element of
we can pick the context
and reflect
every
variable in
to obtain a semantic version of the identity substitution; and from the
resulting element of
we can reify
a normal
form
which is exactly what we wanted! We’re finally done
.
idsec : (Γ : Cx) → ⟦ ⟦ Γ ⟧ᶜ ⟧₀ .dom ʻ Γ idsecβ : (Γ : Cx) → ⟦ ⟦ Γ ⟧ᶜ ⟧₀ .map .η Γ (idsec Γ) ≡ `id
idsec ∅ = lift tt idsec (Γ , x) = (⟦ ⟦ Γ ⟧ᶜ ⟧₀ .dom ⟪ drop stop ⟫ idsec Γ) , Nfa.reflect (normalisation x) (var stop) idsecβ ∅ = `!-η _ idsecβ (Γ , x) = ap₂ _`,_ (Γ'.map .is-natural _ _ _ ·ₚ _ ∙ ap₂ _`∘_ (idsecβ Γ) refl ∙ Syn.cancell `idl ∙ Syn.intror refl) (Nfa.com₀ (normalisation x) (var stop) ∙ Syn.intror refl) ∙ sym `πη where module Γ' = /-Obj ⟦ ⟦ Γ ⟧ᶜ ⟧₀
nfᶜ : ∀ Γ {σ : Ty} (e : Mor ⟦ Γ ⟧ᶜ σ) → Σ[ n ∈ Nf Γ σ ] e ≡ ⟦ n ⟧ₙ nfᶜ Γ {σ} e = record { fst = done ; snd = sym sq } where module σ = Nfa (normalisation σ) done = σ.reify (⟦ e ⟧₁ .map .η Γ (idsec Γ))
To show that the denotation of this normal form is the map we started with, we can calculate with the definition of morphisms in and the two triangles for the normalisation algebras on the domain and codomain, one each.
abstract sq : ⟦ done ⟧ₙ ≡ e sq = ⟦ done ⟧ₙ ≡⟨ sym (σ.com₁ _) ⟩≡ σ.⟦ ⟦ e ⟧₁ .map .η Γ (idsec Γ) ⟧ₚ ≡⟨ unext (⟦ e ⟧₁ .com) _ _ ⟩≡ e `∘ ⟦ ⟦ Γ ⟧ᶜ ⟧₀ .map .η Γ (idsec Γ) ≡⟨ Syn.elimr (idsecβ Γ) ⟩≡ e ∎
Finally, using the trivial isomorphism we can normalise arbitrary morphisms
nf : ∀ {τ σ : Ty} (e : Mor τ σ) → Σ[ n ∈ Nf (∅ , τ) σ ] e ≡ ⟦ n ⟧ₙ `∘ (`! `, `id) nf {τ} {σ} e = let n , p = nfᶜ (∅ , τ) (e `∘ `π₂) in n , Equiv.adjunctl (dom-iso→hom-equiv Free-ccc Synᵐ.λ≅) p
Stability🔗
We can go one step further and show that our normalisation procedure
enjoys stability: normalising a normal form leaves it
unchanged.
stability : (n : Nf Γ τ) → nfᶜ Γ ⟦ n ⟧ₙ .fst ≡ n
stability-ne
: (n : Ne Γ τ)
→ ⟦ ⟦ n ⟧ₛ ⟧₁ .map .η Γ (idsec Γ) ≡ Nfa.reflect (normalisation τ) n
stability-var
: (x : Var Γ τ)
→ ⟦ ⟦ x ⟧ⁿ ⟧₁ .map .η Γ (idsec Γ) ≡ Nfa.reflect (normalisation τ) (var x)
stability (lam n) = ap lam (stability n) stability (pair n n') = ap₂ pair (stability n) (stability n') stability unit = refl stability (ne x) = stability-ne x stability-ne (var x) = stability-var x stability-ne {Γ = Γ} {τ = τ} (app {τ = σ} n x) = ⟦ ⟦ app n x ⟧ₛ ⟧₁ .map .η Γ (idsec Γ) ≡⟨⟩ ev' .map .η Γ (⟦ ⟦ n ⟧ₛ ⟧₁ .map .η Γ (idsec Γ) , ⟦ ⟦ x ⟧ₙ ⟧₁ .map .η Γ (idsec Γ)) ≡⟨ ap (λ a → ev' .map .η Γ (a , ⟦ ⟦ x ⟧ₙ ⟧₁ .map .η Γ (idsec Γ))) (stability-ne n) ⟩≡ ev' .map .η Γ (σ⇒τ.reflect n , ⟦ ⟦ x ⟧ₙ ⟧₁ .map .η Γ (idsec Γ)) ≡⟨⟩ τ.reflect (app (ren-ne stop n) (nfᶜ Γ ⟦ x ⟧ₙ .fst)) ≡⟨ ap τ.reflect (ap₂ app (ren-ne-stop stop n) (stability x)) ⟩≡ τ.reflect (app n x) ∎ where module τ = Nfa (normalisation τ) module σ⇒τ = Nfa (normalisation (σ `⇒ τ)) open Cartesian-closed-over _ _ {Free-closed} GlTm-closed stability-ne (hom x n) = ap (ne ⊙ hom x) (stability n) stability-ne (fstₙ n) = ap fst (stability-ne n) stability-ne (sndₙ n) = ap snd (stability-ne n) stability-var stop = refl stability-var {Γ = Γ , σ} {τ = τ} (pop x) = (⟦ ⟦ x ⟧ⁿ ⟧₁ .map .is-natural Γ (Γ , σ) (drop stop) $ₚ idsec Γ) ∙ ap (⟦ τ ⟧₀ .dom .F₁ (drop stop)) (stability-var x) ∙ sym (Nfa.reflectₙ (normalisation τ))
This lets us conclude with a proper equivalence between morphisms and normal forms.
nf≃ : Mor ⟦ Γ ⟧ᶜ σ ≃ Nf Γ σ nf≃ = Iso→Equiv (fst ⊙ nfᶜ _ , iso ⟦_⟧ₙ stability (sym ⊙ snd ⊙ nfᶜ _))
Applications🔗
Because we went out of our way to get a universal property for the
syntax which computes nicely, we can play around with the normalisation
function, as long as all the types are (Agda) normal forms. For example,
the normal form of the identity function on pairs is the pair
of the fstₙ
and sndₙ
projections of the input
“variable”, which are both neutral. We can also test that, e.g., the
function
6 as an eta-epansion, has the same
normal form as the identity map; and that this normal form, as we
expect, is “the function which applies the second most-recent variable
to the most recent”.
module _ (A B : Node) where _ : nf {τ = ` A `× ` A} {σ = ` A `× ` A} `id .fst ≡ pair (ne (fstₙ (var stop))) (ne (sndₙ (var stop))) _ = refl _ : nf {τ = ` A `⇒ ` B} {σ = ` A `⇒ ` B} (`ƛ (`ev `∘ (`π₁ `, `π₂))) .fst ≡ nf {τ = ` A `⇒ ` B} {σ = ` A `⇒ ` B} `id .fst _ = refl _ : nf {τ = ` A `⇒ ` B} {σ = ` A `⇒ ` B} `id .fst ≡ lam (ne (app (var (pop stop)) (ne (var stop)))) _ = refl
Since normal forms are all unquotiented, first-order data, we can
decide their equality. And since every morphism is the denotation of
some normal form, we can decide equality in Mor
, too.
instance Discrete-Mor : ∀ ⦃ _ : Discrete Node ⦄ → ⦃ _ : ∀ {x y} → Discrete (Edge x y) ⦄ → ∀ {τ σ} → Discrete (Mor τ σ) Discrete-Mor = Discrete-inj (fst ⊙ nf) (λ {x} {y} p → nf x .snd ∙∙ ap (λ e → ⟦ e ⟧ₙ `∘ (`! `, `id)) p ∙∙ sym (nf y .snd)) auto
While most applications of this equality decision procedure rely on
having an actual
with concrete implementations of decidable equality on base types and
base terms, we can give some examples, both positive and
negative, which are completely independent of both the choice of
and of the actual implementations of decidable equality for
base things. For example, we know by `!-η
that all morphisms into (a
function type ending in) the unit type are identical, and indeed even
with “assumed” decidable equality we can compute this, even if the
domains are neutral types:
module _ ⦃ _ : Discrete Node ⦄ ⦃ _ : ∀ {x y} → Discrete (Edge x y) ⦄ where _ : ∀ {A B : Ty} {f g} → Path (Mor A (B `⇒ `⊤)) f g _ = decide!
Moreover, if we work instead against an assumed base type, so that both the domain and codomain have normalisation algebras that Agda can fully evaluate, we can e.g. separate the first and second projections on product types. This is true even if the base terms are such that this base type has a single inhabitant, because we did not (and, in general, can not) equip base types with eta laws — so in a non-empty context, base types can have distinct neutrals.
_ : ∀ {t : Node} → _≠_ {A = Mor ((` t) `× (` t)) (` t)} `π₁ `π₂ _ = decide!
One downside of our categorical approach to normalisation is that we cannot directly use it to write a solver for morphisms in an arbitrary Cartesian closed category, because the collection of objects does not necessarily form a set. We define a solver in a separate module, based on the same ideas.
We also define the simply-typed -calculus in a separate module as an alternative presentation of the free CCC and show how it relates to
We will use greek letters to stand for variables of type
Ty
, and upright serif letters to stand for the base types and base terms fromTo avoid name clashes with the
Ob
andHom
fields of precategories, this formalisation refers to base types as drawn fromNode
and base terms fromEdge
.This is justified by observing that any graph generates a taking the nodes as the base types, and defining the base terms so that there is an equivalence between the sets and ↩︎
The construction of the normalisation procedure does not depend on having decidable equality.↩︎
We could thus define the type of normal forms by recursion on the type, rather than by induction, but we stick with the inductive definition to make pattern matching slightly more convenient.↩︎
If necessary to avoid clashes with other syntax, we may write context extensions as the “nameless” instead of using parentheses around the wordier ↩︎
In the formalisation we reserve the imperative forms
reify
andreflect
for the components of these transformations.↩︎Note that in both of these examples we have to “fill out” our examples with base types under the
`_
constructor, and not simply assumed simple types, because ending up with a nice normal form requires that Agda compute the normalisation algebras into one of the concrete cases we defined above.The normal form of the identity in is still a
lam
constructor, but its body contains many redundant reify/reflect pairs that will only cancel out when and are normal forms.↩︎
References
- Čubrić, Djordje, Peter Dybjer, and Philip Scott. 1998. “Normalization and the Yoneda Embedding.” Mathematical. Structures in Comp. Sci. 8 (2): 153–92. https://doi.org/10.1017/S0960129597002508.