module Cat.CartesianClosed.Free {β„“} (S : Ξ»-Signature β„“) where

Free cartesian closed categoriesπŸ”—

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 Morphism 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

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.
    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.
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
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

These are required to make the following triangles commute.

    comβ‚€ : {Ξ“ : Cx} (n : Ne Ξ“ Ο„)     β†’ ⟦ reflect n βŸ§β‚š ≑ ⟦ n βŸ§β‚›
    com₁ : {Ξ“ : Cx} (x : P .dom Κ» Ξ“) β†’ ⟦ x βŸ§β‚š ≑ ⟦ reify x βŸ§β‚™

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.

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
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


  1. 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 from

    To avoid name clashes with the Ob and Hom fields of precategories, this formalisation refers to base types as drawn from Node and base terms from Edge.

    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 β†©οΈŽ

  2. The construction of the normalisation procedure does not depend on having decidable equality.β†©οΈŽ

  3. 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.β†©οΈŽ

  4. If necessary to avoid clashes with other syntax, we may write context extensions as the β€œnameless” instead of using parentheses around the wordier β†©οΈŽ

  5. In the formalisation we reserve the imperative forms reify and reflect for the components of these transformations.β†©οΈŽ

  6. 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.