-- For computing with terms, in particular, for β-reduction, we need -- to substitute terms for variables. -- -- Given t : Term (a ∷ Γ) b and u : Term Γ a we wish to define substitution -- for de Bruijn index zero : a ∈ (a ∷ Γ), namely t [ u ]₀ : Term Γ b. -- This then allows us to define β-reduction app (abs t) u ⟶ t [ u ]₀. -- (Observe that abs t : Term Γ (a ⇒ b).) -- -- However, substitution for index 0 cannot be defined directly, because -- (abs t) [ u ]₀ will require to replace index 1 in t by u. -- Moreover, abs (t [ u ]₁) would not be correct yet since u's own index 0 -- would be captured by "abs" while it is supposed to be a free index. -- We need thus to increase all indices in u by 1, correct would then be -- abs (t [ lift u ]₁) where "lift" does the increment. -- -- Increasing de Bruijn indices could also be implemented by substitution, -- but then we would need a substitution that can replace all indices -- rather than just a particular one. -- This is often called "simultaneous substitution or parallel substitution". -- -- We will take this concept as our primitive and write σ : Sub Γ Δ for -- a (parallel) substitution which replaces each index x : a ∈ Δ by a term -- t : Term Γ a, so we get the substitution operation -- -- sub : (σ : Sub Γ Δ) (t : Term Δ a) → Term Γ a . -- -- In the case of abstraction, we get -- -- sub (σ : Sub Γ Δ) (abs t : Term Δ (a ⇒ b)) : Term Γ (a ⇒ b) -- = abs (sub (lift σ : Sub (a ∷ Γ) (a ∷ Δ)) -- (t : Term (a ∷ Δ) b)) -- -- The idea is that lift σ replaces index 0 by itself and index x+1 by -- σ(x) where each index in σ(x) is incremented. -- -- We take the simplest idea of representing substitutions as just a list -- of terms where each term in the list is the replacement for one de Bruijn index. -- -- [] : Sub Γ [] (no indices to replace) -- -- (t ∷ σ) : Sub Γ (a ∷ Δ) when t : Term Γ a and σ : Sub Γ Δ. -- -- In the second rule, t is the replacement for index 0 and σ the one for the other indices. -- -- Substitution can be composed by applying a substitution to each term of the other substitution: -- -- compSS : Sub Γ Δ → Sub Δ Φ → Sub Γ Φ -- compSS σ [] = [] -- compSS σ (t ∷ σ') = sub σ t ∷ compSS σ σ' -- -- The substitution -- -- skip1 : Sub (a ∷ Γ) Γ -- skip1 = var 1 ∷ var 2 ∷ ... ∷ var |Γ| ∷ [] -- -- would do the job of incrementing each index by 1. -- Thus, we could define -- -- lift : Sub Γ Δ → Sub (a ∷ Γ) (a ∷ Δ) -- lift σ = var zero ∷ compSS skip1 σ -- -- and use lift in the definition of sub for abstraction (see above). -- -- Unfortunately, such a definition would be rejected by Agda's termination checker. -- We are defining sub, lift, compSS by mutual recursion and termination is not obvious. -- To see why the definitions are well-founded, we need to observe that skip1 is -- a "variable substitution" and so "compSS skip1 σ" does not morally increase the -- size of the substitution σ. With some indexing tricks we could make this work. -- -- However, we take a more conservative approach and define "variable substitutions" -- completely separately, in a restricted form called "weakenings" (module Term.Weakenings), -- Wk Γ Δ. These can be turned into substitutions with fromWk : Wk Γ Δ → Sub Γ Δ. -- They allow us to define "lift" before "sub" breaking the cycle. module Term.Substitution where open import Prelude open import Term open import Term.Weakening using (Wk; done; keep; skip; skip1; wk; idW; compWW) renaming (lookup to lookupR) private variable a b : Ty Γ Δ Φ : Context -- "Parallel" / "simulatenous" substitutions Sub Γ Δ -- are lists of terms living in Γ, -- one for each variable bound in Δ. data Sub : (Γ Δ : Context) → Set where [] : Sub Γ [] _∷_ : (t : Term Γ a) (σ : Sub Γ Δ) → Sub Γ (a ∷ Δ) -- compWS ρ σ weakens substitution σ according to ρ, -- i.e., applies the weakening ρ to each term in σ. compWS : Wk Γ Δ → Sub Δ Φ → Sub Γ Φ compWS ρ [] = [] compWS ρ (t ∷ σ) = wk ρ t ∷ compWS ρ σ -- We introduce a shorthand "weak σ" to weaken a substitution σ -- by just one new variable. weak : Sub Γ Δ → Sub (a ∷ Γ) Δ weak = compWS skip1 -- "lift σ" lifts substitution σ under a binder such as "abs". -- The de Bruijn index 0 bound here is mapped to itself, -- all other de Bruijn indices are incremented. lift : Sub Γ Δ → Sub (a ∷ Γ) (a ∷ Δ) lift σ = var zero ∷ weak σ -- "lookup σ x" returns the term t : Term Γ a -- the variable x : a ∈ Δ is mapped to by substitution σ : Sub Γ Δ. -- In other worss, "lookup σ" interprets σ as function -- from indices to terms. lookup : Sub Γ Δ → a ∈ Δ → Term Γ a lookup [] () lookup (t ∷ σ) zero = t lookup (t ∷ σ) (suc x) = lookup σ x -- "sub σ t" carries out the substitution σ in term t. -- So, "sub σ" interprets σ as function from terms to terms. sub : Sub Γ Δ → Term Δ a → Term Γ a sub σ (var x) = lookup σ x sub σ (abs t) = abs (sub (lift σ) t) sub σ (app t u) = app (sub σ t) (sub σ u) -- Weakenings can be seen as special substitutions ("variable substitutions"). fromWk : (ρ : Wk Γ Δ) → Sub Γ Δ fromWk done = [] fromWk (skip ρ) = weak (fromWk ρ) fromWk (keep ρ) = lift (fromWk ρ) -- Substitutions Sub Γ Δ form a category where contexts are the objects -- and substitutions the morphisms. idS : Sub Γ Γ idS = fromWk idW compSS : Sub Γ Δ → Sub Δ Φ → Sub Γ Φ compSS σ [] = [] compSS σ (t ∷ τ) = sub σ t ∷ compSS σ τ -- The singleton substitution is the substitution we are mainly interested in. sg : Term Γ a → Sub Γ (a ∷ Γ) sg t = t ∷ idS -- Substituting a term for de Bruijn index 0. infixr 10 _[_]₀ _[_]₀ : Term (a ∷ Γ) b → Term Γ a → Term Γ b t [ u ]₀ = sub (sg u) t -- We can restrict a substitution σ : Sub Γ Δ -- by a weakening ρ : Wk Δ Φ that selects -- some terms from the list σ. -- This gives the restricted substitution compSW σ ρ : Sub Γ Φ -- that only substitutes for the variables in Φ (sublist of Δ). compSW : Sub Γ Δ → Wk Δ Φ → Sub Γ Φ compSW [] done = [] compSW (t ∷ σ) (skip ρ) = compSW σ ρ compSW (t ∷ σ) (keep ρ) = t ∷ compSW σ ρ