Occurrence counting and family substitution
DefinitionMSKleene_SubstFamOccurrence counting and family substitution for a single variable (the operator of Definition 3.13, used in Lemma 3.23, Corollary 3.17 and Lemma 3.18).
Term.occ z P is , the number of occurrences of the variable z in P. substFam z P qs replaces, for every , the -th occurrence of z in P (in left-to-right order) by the term qs α; it is implemented by threading the list List.ofFn qs through the term.
Formalization Note noncomputable because the variable-equality test uses classical decidability.
/-
Occurrence counting and family substitution for a single variable
(the operator `⟨z/(Q_α)_{α ∈ |P|_z}⟩(P)` of Definition 3.13, used in
Lemma 3.23, Corollary 3.17 and Lemma 3.18).
`Term.occ z P` is `|P|_z`, the number of occurrences of the variable `z` in `P`.
`substFam z P qs` replaces, for every `α`, the `α`-th occurrence of `z` in `P`
by the term `qs α`.
-/
import Definitions.Def_MSKleene_Term
import Mathlib.Data.List.OfFn
namespace MSKleene
open Classical
universe u
variable {S : Type u} {sig : Signature S} {X : SSet S}
-- `Term.occ z P` = `|P|_z`, the number of occurrences of the variable `z`
-- (of sort `v`) in the term `P`.
mutual
noncomputable def Term.occ {v : S} (z : X v) : {s : S} → Term sig X s → ℕ
| s, .var x => if h : s = v then (if (h ▸ x) = z then 1 else 0) else 0
| _, .app _ ts => TermVec.occ z ts
noncomputable def TermVec.occ {v : S} (z : X v) : {w : List S} → TermVec sig X w → ℕ
| _, .nil => 0
| _, .cons t ts => Term.occ z t + TermVec.occ z ts
end
-- Auxiliary for `substFam`: substitute the terms of the list `qs` for the
-- successive occurrences of `z`, from left to right, returning the substituted
-- term and the unconsumed tail of `qs`.
mutual
noncomputable def Term.substFamAux {v : S} (z : X v) :
{s : S} → Term sig X s → List (Term sig X v) →
Term sig X s × List (Term sig X v)
| s, .var x, qs =>
if h : s = v then
(if (h ▸ x) = z then
(match qs with
| [] => (Term.var x, [])
| q :: qs' => (h.symm ▸ q, qs'))
else (Term.var x, qs))
else (Term.var x, qs)
| _, .app σ ts, qs =>
let r := TermVec.substFamAux z ts qs
(Term.app σ r.1, r.2)
noncomputable def TermVec.substFamAux {v : S} (z : X v) :
{w : List S} → TermVec sig X w → List (Term sig X v) →
TermVec sig X w × List (Term sig X v)
| _, .nil, qs => (.nil, qs)
| _, .cons t ts, qs =>
let r1 := Term.substFamAux z t qs
let r2 := TermVec.substFamAux z ts r1.2
(.cons r1.1 r2.1, r2.2)
end
/-- `substFam z P qs` — the substitution of the family `qs` for `z` in `P`
(Definition 3.13): the `α`-th occurrence of `z` in `P` is replaced by `qs α`. -/
noncomputable def substFam {v s : S} (z : X v) (P : Term sig X s)
(qs : Fin (Term.occ z P) → Term sig X v) : Term sig X s :=
(Term.substFamAux z P (List.ofFn qs)).1
end MSKleene
Read-back
What the Lean code literally says, in plain math · claude-sonnet-5
Term.occ
Work in a fixed sort type , with an implicit signature (which assigns to each arity list and each result sort a type of operation symbols) and an implicit sorted family of variable types . A term of sort (an element of ) is either a variable with , or an application with and a vector of terms whose sorts are listed by (an element of ). The function Term.occ takes an implicit sort , a distinguished variable , an implicit sort , and a term , and returns a natural number, written here . It is defined by recursion on :
- If with : if the sorts satisfy , take the proof , transport along to get , and return if and otherwise; if , return .
- If : return using the
TermVecversion below; the operation symbol itself contributes nothing.
Thus counts the variable-leaf occurrences in that are exactly (matching both the sort and the value ). Equality of sorts and of elements of is decided classically (the definition is noncomputable and opens Classical).
TermVec.occ
With the same ambient data, TermVec.occ takes an implicit sort , a distinguished variable , an implicit arity list , and a term vector (either the empty vector , or with a term and a shorter vector), returning a natural number. It is defined by recursion on :
- On the empty vector : the value is .
- On : the value is , i.e. the occurrence count of in the head term plus the occurrence count of in the tail vector .
So is the total number of occurrences of the variable across all component terms of the vector. It is noncomputable and uses classical decidability.
Term.substFamAux
With ambient , , as above, Term.substFamAux takes an implicit sort , a distinguished variable , an implicit sort , a term , and a list of candidate replacement terms of sort . It returns an ordered pair whose first component is a term of and whose second component is a list in (the unconsumed remainder of ). It is defined by recursion on :
- If with :
- If , with proof :
- If :
- If is empty : return — the variable is left unchanged and the empty list is returned.
- If : return , where and is transported along to become a term of sort ; the remaining list is (one element consumed from the front).
- If : return — variable unchanged, list untouched.
- If :
- If : return — variable unchanged, list untouched.
- If , with proof :
- If : compute , and return , where is the substituted argument vector and is the leftover list from processing .
So this replaces occurrences of inside one at a time, each occurrence consuming the current head of the threaded list; if the list is exhausted an occurrence is left as . It is noncomputable and uses classical decidability of the sort equality and of the equality in .
TermVec.substFamAux
With the same ambient data, TermVec.substFamAux takes an implicit sort , a distinguished variable , an implicit arity list , a term vector , and a list . It returns an ordered pair whose first component is a term vector in and whose second component is a list in . It is defined by recursion on :
- On the empty vector : return (nothing substituted, list unchanged).
- On : first compute (substitute into the head term , threading the whole list ); then compute (substitute into the tail vector , threading the list left over from the head, namely ); finally return .
Consequently the replacement list is threaded strictly left-to-right and depth-first: within a , the head term is fully processed before the tail; within an (via Term.substFamAux), the argument vector is processed in this same order. Each successive occurrence of takes the next element from the front of the list, and once the list is empty every further occurrence of is kept unchanged; all non- leaves and all operation nodes are rebuilt identically. It is noncomputable.
substFam
With ambient , , as above, substFam takes two implicit sorts , a distinguished variable , a term , and a family
that is, a replacement term of sort for each index with , where is the occurrence count defined above. It returns a term of , defined as the first component of
where is the list of the family's values taken in index order (of length exactly ). The second component returned by (the unconsumed tail) is discarded. By the threading described above, the result is with its -th occurrence of (in the left-to-right depth-first traversal order) replaced by , for each . Degenerate case: if , then is empty, is the empty family, , and the output is (rebuilt without any change). The definition is noncomputable and relies on classical decidability of equality on and on .
Confirmed by the mission captain (proposal self-audit).