module Algorithmic where
open import Relation.Binary.PropositionalEquality using (_≡_;refl;sym;trans;cong;cong₂;subst) open import Data.Empty using (⊥) open import Data.Fin using (Fin) open import Data.Product using (_×_) open import Data.Vec as Vec using (Vec;[];_∷_;lookup) open import Data.List.Properties using (foldr-++) open import Utils renaming (_×_ to _U×_; List to UList; Array to UArray; map to umap) open import Utils.List using (List;_++_;foldr;IList;[];_∷_;Bwd;_:<_;bwd-foldr;lemma-bwd-foldr;_<><_;lemma<>1) open import Type using (Ctx⋆;∅;_,⋆_;_⊢⋆_;_∋⋆_;Z;S;Φ) open _⊢⋆_ open import Type.BetaNormal using (_⊢Nf⋆_;_⊢Ne⋆_;weakenNf;renNf;embNf) open _⊢Nf⋆_ open _⊢Ne⋆_ import Type.RenamingSubstitution as ⋆ open import Type.BetaNBE using (nf) open import Type.BetaNBE.RenamingSubstitution using (subNf∅) renaming (_[_]Nf to _[_]) open import Builtin using (Builtin) open import Builtin.Constant.Type using (TyCon) open TyCon open import Builtin.Constant.AtomicType using (⟦_⟧at) open import Builtin.Signature using (_⊢♯) open _⊢♯ open import Algorithmic.Signature using (btype;⊢♯2TyNe♯)
To begin, we get all our infix declarations out of the way. We list separately operators for judgements, types, and terms.
infix 4 _∋_ infix 4 _⊢_ infixl 5 _,_
We need to mutually define contexts and their erasure to type contexts.
--data Ctx : Set --∥_∥ : Ctx → Ctx⋆
A context is either empty, or extends a context by a type variable of a given kind, or extends a context by a variable of a given type.
data Ctx : Ctx⋆ → Set where ∅ : Ctx ∅ _,⋆_ : Ctx Φ → (J : Kind) → Ctx (Φ ,⋆ J) _,_ : (Γ : Ctx Φ) → Φ ⊢Nf⋆ * → Ctx Φ
Let Γ range over contexts. In the last rule,
the type is indexed by the erasure of the previous
context to a type context and a kind.
The erasure of a context is a type context.
--∥ ∅ ∥ = ∅ --∥ Γ ,⋆ J ∥ = ∥ Γ ∥ ,⋆ J --∥ Γ , A ∥ = ∥ Γ ∥
A variable is indexed by its context and type.
data _∋_ : (Γ : Ctx Φ) → Φ ⊢Nf⋆ * → Set where
Z : ∀ {Γ} {A : Φ ⊢Nf⋆ *}
----------
→ Γ , A ∋ A
S : ∀ {Γ} {A : Φ ⊢Nf⋆ *} {B : Φ ⊢Nf⋆ *}
→ Γ ∋ A
----------
→ Γ , B ∋ A
T : ∀ {Γ K}{A : Φ ⊢Nf⋆ *}
→ Γ ∋ A
-------------------
→ Γ ,⋆ K ∋ weakenNf A
We define a predicate ♯Kinded for kinds that ultimately end in ♯.
data ♯Kinded : Kind → Set where
♯ : ♯Kinded ♯
K♯ : ∀{K J} → ♯Kinded J → ♯Kinded (K ⇒ J)
There is no type of a ♯Kinded kind which takes more than two type arguments.
lemma♯Kinded : ∀ {K K₁ K₂ J} → ♯Kinded J → ∅ ⊢Ne⋆ (K₂ ⇒ (K₁ ⇒ (K ⇒ J))) → ⊥
lemma♯Kinded k (f · _) = lemma♯Kinded (K♯ k) f
Closed types can be mapped into the signature universe and viceversa.
ty2sty : ∅ ⊢Nf⋆ ♯ → 0 ⊢♯ ty2sty (ne (((f · _) · _) · _)) with lemma♯Kinded ♯ f ... | () ty2sty (ne ((^ pair · x) · y)) = pair (ty2sty x) (ty2sty y) ty2sty (ne (^ list · x)) = list (ty2sty x) ty2sty (ne (^ array · x)) = array (ty2sty x) ty2sty (ne (^ (atomic x))) = atomic x sty2ty : 0 ⊢♯ → ∅ ⊢Nf⋆ ♯ sty2ty t = ne (⊢♯2TyNe♯ t)
Now we have functions ty2sty and sty2ty. We prove that they are inverses, and therefore
define an isomorphism.
ty≅sty₁ : ∀ (A : ∅ ⊢Nf⋆ ♯) → A ≡ sty2ty (ty2sty A) ty≅sty₁ (ne (((f · _) · _) · _)) with lemma♯Kinded ♯ f ... | () ty≅sty₁ (ne ((^ pair · x) · y)) = cong ne (cong₂ _·_ (cong (^ pair ·_) (ty≅sty₁ x)) (ty≅sty₁ y)) ty≅sty₁ (ne (^ list · x)) = cong ne (cong (^ list ·_) (ty≅sty₁ x)) ty≅sty₁ (ne (^ array · x)) = cong ne (cong (^ array ·_) (ty≅sty₁ x)) ty≅sty₁ (ne (^ (atomic x))) = refl ty≅sty₂ : ∀ (A : 0 ⊢♯) → A ≡ ty2sty (sty2ty A) ty≅sty₂ (atomic x) = refl ty≅sty₂ (list A) = cong list (ty≅sty₂ A) ty≅sty₂ (array A) = cong array (ty≅sty₂ A) ty≅sty₂ (pair A B) = cong₂ pair (ty≅sty₂ A) (ty≅sty₂ B)
The semantics of closed types of kind ♯ is given by the following interpretation function
⟦_⟧ : (ty : ∅ ⊢Nf⋆ ♯) → Set ⟦ ne (((f · _) · _) · _) ⟧ with lemma♯Kinded ♯ f ... | () ⟦ ne ((^ pair · x) · y) ⟧ = ⟦ x ⟧ U× ⟦ y ⟧ ⟦ ne (^ list · x) ⟧ = UList ⟦ x ⟧ ⟦ ne (^ array · x) ⟧ = UArray ⟦ x ⟧ ⟦ ne (^ (atomic x)) ⟧ = ⟦ x ⟧at
All these types need to be able to be interfaced with Haskell, as this interpretation function is used everywhere of type or a type tag needs to be interpreted. It is precisely because they need to be interfaced with Haskell that we use the version of product and list from the Utils module.
A term is indexed over by its context and type. A term is a variable, an abstraction, an application, a type abstraction, a type application, a wrapping or unwrapping of a recursive type, a constructor, a case expression, a constant, a builtin function, or an error.
Constants of a builtin type A are given directly by its meaning ⟦ A ⟧, where A is restricted to kind ♯.
The type of cases if a function consuming every type in a list. We construct it with the following function:
mkCaseType : ∀{Φ} (A : Φ ⊢Nf⋆ *) → List (Φ ⊢Nf⋆ *) → Φ ⊢Nf⋆ *
mkCaseType A = foldr _⇒_ A
We declare two auxiliary datatypes, which are mutually recursive with the type of terms, for constructor arguments and cases.
ConstrArgs : (Γ : Ctx Φ) → List (Φ ⊢Nf⋆ *) → Set
data Cases (Γ : Ctx Φ) (B : Φ ⊢Nf⋆ *) : ∀{n} → Vec (List (Φ ⊢Nf⋆ *)) n → Set
The actual type of terms
infixl 7 _·⋆_/_
data _⊢_ (Γ : Ctx Φ) : Φ ⊢Nf⋆ * → Set where
` : ∀ {A : Φ ⊢Nf⋆ *}
→ Γ ∋ A
-----
→ Γ ⊢ A
ƛ : ∀ {A B : Φ ⊢Nf⋆ *}
→ Γ , A ⊢ B
---------
→ Γ ⊢ A ⇒ B
_·_ : ∀ {A B : Φ ⊢Nf⋆ *}
→ Γ ⊢ A ⇒ B
→ Γ ⊢ A
---------
→ Γ ⊢ B
Λ : ∀ {K}
→ {B : Φ ,⋆ K ⊢Nf⋆ *}
→ Γ ,⋆ K ⊢ B
-------------------
→ Γ ⊢ Π B
_·⋆_/_ : ∀ {K C}
→ {B : Φ ,⋆ K ⊢Nf⋆ *}
→ Γ ⊢ Π B
→ (A : Φ ⊢Nf⋆ K)
→ C ≡ B [ A ]
--------------
→ Γ ⊢ C
wrap : ∀{K}
→ (A : Φ ⊢Nf⋆ (K ⇒ *) ⇒ K ⇒ *)
→ (B : Φ ⊢Nf⋆ K)
→ Γ ⊢ nf (embNf A · ƛ (μ (embNf (weakenNf A)) (` Z)) · embNf B)
-------------------------------------------------------------
→ Γ ⊢ μ A B
unwrap : ∀{K C}
→ {A : Φ ⊢Nf⋆ (K ⇒ *) ⇒ K ⇒ *}
→ {B : Φ ⊢Nf⋆ K}
→ Γ ⊢ μ A B
→ C ≡ nf (embNf A · ƛ (μ (embNf (weakenNf A)) (` Z)) · embNf B)
-------------------------------------------------------------
→ Γ ⊢ C
constr : ∀{n}
→ (i : Fin n) -- The tag
→ (Tss : Vec (List (Φ ⊢Nf⋆ *)) n) -- The types of the sum of products.
-- We make it a Vector of lists, so that
-- the tag is statically correct.
-- We use the name `Tss` as it stands for a
-- a container of containers of types.
→ ∀ {ts} → ts ≡ lookup Tss i -- The reason to define it like this, rather than
→ ConstrArgs Γ ts -- simply ConstrArgs Γ (lookup Tss i) is so that it
-- is easier to construct terms (helps to avoid the use of subst)
-- as often the result of a function will not match definitionally
-- with (lookup Tss i) but only propositionally.
--------------------------------------
→ Γ ⊢ SOP Tss
case : ∀{n}{Tss : Vec _ n}{A : Φ ⊢Nf⋆ *}
→ (t : Γ ⊢ SOP Tss) -- The term we case on
→ (cases : Cases Γ A Tss)
--------------------------
→ Γ ⊢ A
con : ∀{A : ∅ ⊢Nf⋆ ♯}{B}
→ ⟦ A ⟧
→ B ≡ subNf∅ A
---------------------
→ Γ ⊢ con B
builtin_/_ : ∀{C}
→ (b : Builtin)
→ C ≡ btype b
--------------
→ Γ ⊢ C
error :
(A : Φ ⊢Nf⋆ *)
--------------
→ Γ ⊢ A
-- The type of arguments to a `constr` constructor
ConstrArgs Γ = IList (Γ ⊢_)
-- Cases is indexed by a vector
-- so it can't be an IList
data Cases Γ B where
[] : Cases Γ B []
_∷_ : ∀{n}{Ts}{Tss : Vec _ n}(
c : Γ ⊢ (mkCaseType B Ts))
→ (cs : Cases Γ B Tss)
---------------------
→ Cases Γ B (Ts ∷ Tss)
Utility functions
conv∋ : ∀ {Γ Γ'}{A A' : Φ ⊢Nf⋆ *}
→ Γ ≡ Γ'
→ A ≡ A'
→ Γ ∋ A
→ Γ' ∋ A'
conv∋ refl refl x = x
conv⊢ : ∀ {Γ Γ'}{A A' : Φ ⊢Nf⋆ *}
→ Γ ≡ Γ'
→ A ≡ A'
→ Γ ⊢ A
→ Γ' ⊢ A'
conv⊢ refl refl t = t
lookupCase : ∀{n Φ}{Γ : Ctx Φ}{A}{Tss : Vec _ n} → (i : Fin n) → Cases Γ A Tss → Γ ⊢ mkCaseType A (Vec.lookup Tss i)
lookupCase Fin.zero (c ∷ cs) = c
lookupCase (Fin.suc i) (c ∷ cs) = lookupCase i cs
bwdMkCaseType : ∀{Φ} → Bwd (Φ ⊢Nf⋆ *) → (A : Φ ⊢Nf⋆ *) → Φ ⊢Nf⋆ *
bwdMkCaseType bs A = bwd-foldr _⇒_ A bs
lemma-bwdfwdfunction' : ∀{Φ} {B : Φ ⊢Nf⋆ *} Ts → mkCaseType B Ts ≡ bwdMkCaseType ([] <>< Ts) B
lemma-bwdfwdfunction' {B = B} Ts = trans (cong (mkCaseType B) (sym (lemma<>1 [] Ts))) (lemma-bwd-foldr _⇒_ B ([] <>< Ts))
constr-cong : ∀{Γ : Ctx Φ}{n}{i : Fin n}{Tss : Vec (List (Φ ⊢Nf⋆ *)) n}{ts}
→ (p : ts ≡ lookup Tss i)
→ {cs : ConstrArgs Γ ts}
→ {cs' : ConstrArgs Γ (lookup Tss i)}
→ (q : cs' ≡ subst (IList (Γ ⊢_)) p cs)
→ constr i Tss refl cs' ≡ constr i Tss p cs
constr-cong refl refl = refl
constr-cong' : ∀{Γ : Ctx Φ}{n}{i : Fin n}{A : Vec (List (Φ ⊢Nf⋆ *)) n}{ts}{ts'}
→ (p : ts ≡ lookup A i)(p' : ts' ≡ lookup A i)
→ {cs : ConstrArgs Γ ts}
→ {cs' : ConstrArgs Γ ts'}
→ (q : subst (IList (Γ ⊢_)) p' cs' ≡ subst (IList (Γ ⊢_)) p cs)
→ constr i A p' cs' ≡ constr i A p cs
constr-cong' refl refl refl = refl