A choice-free recursion theory for Lecture VII (Scott 1981, PRG-19) #
Why this file exists — we roll our own and reject mathlib's recursion theory
here.
Lecture VII ("Computability in effectively given domains") needs the notions
recursively
decidable and recursively enumerable. mathlib has these (Computable,
ComputablePred,
REPred, Primrec, Partrec), but in mathlib v4.30.0 essentially every
correctness lemma of
that development is proved with tactics (grind, lia) or wrapper lemmas that
open
Classical — e.g. Computable.const, Primrec.const, Nat.unpair_pair,
Nat.sqrt_le, and
Nat.Primrec.add/mul all audit with Classical.choice in their axiom set. This
project's
discipline is to keep data (and as much as possible) choice-free (⊆ {propext, Quot.sound}),
so we deliberately decline mathlib's classical recursion theory and instead build
the slice we need
on the genuinely choice-free foundations: the raw inductives Nat.Primrec /
Nat.Partrec (whose
constructors are choice-free) and Nat.sqrt / Nat.pair / Nat.unpair (whose
definitions are
choice-free). The only tactic we lean on for arithmetic is omega, which is
choice-free here
(unlike grind/lia).
The pattern: a relation on integer indices is recursive (decidable) when it has
a
primitive-recursive characteristic function (Nat.Primrec, lifted to
Nat.Partrec for the
recursive/REPred analogues). Tuples are encoded with Nat.pair (choice-free),
and we reprove the
pairing round-trips (unpair_pair', pair_unpair') choice-free here.
Everything in this file is ⊆ {propext, Quot.sound}.
Choice-free Nat.sqrt correctness #
mathlib's Nat.sqrt.iter_sq_le / Nat.sqrt.lt_iter_succ_sq are the heart of
Nat.sqrt's
correctness, but they use grind / lia (classical). We reprove them with
omega.
Choice-free sqrt.iter lower bound. iter n guess ² ≤ n. Faithful port
of
Nat.sqrt.iter_sq_le, with grind replaced by omega.
Choice-free left-cancellation for < under * (mathlib's
Nat.lt_of_mul_lt_mul_left is
classical). From a*b < a*c deduce b < c, by the contrapositive c ≤ b → a*c ≤ a*b.
Choice-free sqrt.iter upper bound. If n < (guess+1)² then n < (iter n guess + 1)².
Faithful port of Nat.sqrt.lt_iter_succ_sq, with lia replaced by omega.
sqrt n squared is ≤ n (choice-free; mathlib's Nat.sqrt_le is classical).
n < (sqrt n + 1)² (choice-free; mathlib's Nat.lt_succ_sqrt is classical).
The initial guess
2 ^ (log₂ n / 2 + 1) over-shoots √n, which feeds lt_iter_succ_sq.
sqrt (q² + a) = q whenever a ≤ 2q (choice-free; mathlib's
Nat.sqrt_add_eq is
classical).
Choice-free pairing round-trip #
Nat.pair/Nat.unpair are themselves choice-free definitions; only mathlib's
unpair_pair
lemma (via sqrt_add_eq) is classical. We reprove the round-trip on our
choice-free sqrt.
unpair ∘ pair = id (choice-free). Mirrors Nat.unpair_pair.
First projection of the pairing round-trip.
Second projection of the pairing round-trip.
pair ∘ unpair = id (choice-free). Mirrors Nat.pair_unpair.
Choice-free primitive-recursive arithmetic #
mathlib's Nat.Primrec.id/add/mul are proved with by simp, which silently
applies the classical
@[simp] Nat.unpair_pair; so they audit with Classical.choice. We reprove the
few we need using
the choice-free round-trips above. The constructors of Nat.Primrec
(zero/succ/left/right/pair/comp/prec) are choice-free and used directly.
Addition is primitive recursive (choice-free).
Multiplication is primitive recursive (choice-free).
Predecessor n ↦ n - 1 is primitive recursive (choice-free; mathlib's
Nat.Primrec.pred is
classical).
Truncated subtraction is primitive recursive (choice-free; mathlib's
Nat.Primrec.sub is
classical).
a * b = 1 ↔ a = 1 ∧ b = 1 over ℕ (choice-free; avoids the classical
generic
mul_eq_one).
Pointwise primitive-recursive arithmetic combinators #
primrec_add/mul/sub are Nat.unpaired forms; the ₂ variants below take
two primitive
recursive functions f, g and build fun n => f n ⋆ g n directly (composing
through Nat.pair).
These cut the Nat.pair/unpair plumbing in larger constructions (sum's
intersection function and
the list-fold engine). All choice-free.
fun n => f n + g n is primitive recursive.
fun n => f n * g n is primitive recursive.
fun n => f n - g n (truncated) is primitive recursive.
selectFn of primitive-recursive c, a, b is primitive recursive.
"Recursively decidable" predicates (Scott's notion, Definition 7.1) #
A predicate is recursively decidable when it has a primitive-recursive
{0,1}-valued characteristic
function. Tuples are coded by Nat.pair. These are the building blocks Scott's
Definition 7.1 needs
(relations on indices), and the closure lemmas (of_iff, comp = reindex, and)
let us derive the
inclusion- and equality-deciders. All choice-free.
A unary predicate p : ℕ → Prop is recursively decidable.
Equations
- Domain.Recursive.RecDecidable p = ∃ (f : ℕ → ℕ), Nat.Primrec f ∧ ∀ (n : ℕ), p n ↔ f n = 1
Instances For
A binary relation is recursively decidable when its Nat.pair-coding is.
Equations
- Domain.Recursive.RecDecidable₂ r = Domain.Recursive.RecDecidable fun (t : ℕ) => r (Nat.unpair t).1 (Nat.unpair t).2
Instances For
A ternary relation is recursively decidable when its Nat.pair-coding (pair n (pair m k))
is.
Equations
- Domain.Recursive.RecDecidable₃ r = Domain.Recursive.RecDecidable fun (t : ℕ) => r (Nat.unpair t).1 (Nat.unpair (Nat.unpair t).2).1 (Nat.unpair (Nat.unpair t).2).2
Instances For
Recursive decidability transfers across a pointwise logical equivalence.
Reindexing. If p is recursively decidable and g is primitive
recursive, then
fun n => p (g n) is recursively decidable.
Conjunction. Recursive decidability is closed under ∧ (multiply the
{0,1} deciders).
An always-true predicate is recursively decidable (constant decider 1).
Equality of two primitive-recursive functions is recursively decidable.
The {0,1}-valued
characteristic function is 1 - ((a t - b t) + (b t - a t)) (truncated
subtraction), which is 1
exactly when a t = b t; primitive recursive via primrec_sub/primrec_add, and
the biconditional
is omega (which understands truncated subtraction).
Negation. Recursive decidability is closed under ¬ (negate the {0,1}
decider).
Every recursively decidable predicate is decidable (the decider is f n = 1, decidable
equality on ℕ); useful for choice-free De Morgan.
Disjunction. Recursive decidability is closed under ∨, via choice-free
De Morgan
p ∨ q ↔ ¬(¬p ∧ ¬q) (the non-constructive direction uses RecDecidable.em).
"Recursively enumerable" predicates (Scott's notion, Definition 7.2) #
Scott's Definition 7.2 asks for the neighbourhood relation Xₙ f Yₘ to be
recursively
enumerable. We model "recursively enumerable" choice-free as a projection of a
recursively
decidable relation: p is r.e. iff there is a recursively decidable q with
p n ↔ ∃ i, q ⟨i, n⟩ (the search variable i is paired with n via Nat.pair).
This is the
standard equivalent of Scott's prose description — "there is a primitive recursive
r with
y = {Y_{r(i)} ∣ i ∈ ℕ}": the projection form additionally represents the empty
set (take q
identically false), exactly as r.e. sets require. Every recursively decidable
predicate is r.e.
(RecDecidable.re, dropping the search variable), and r.e.-ness transfers across
pointwise
equivalence (REPred.of_iff). All choice-free.
A binary relation is recursively enumerable when its Nat.pair-coding is.
Equations
- Domain.Recursive.REPred₂ r = Domain.Recursive.REPred fun (t : ℕ) => r (Nat.unpair t).1 (Nat.unpair t).2
Instances For
Every recursively decidable predicate is recursively enumerable. Use the
decider as the
relation q ⟨i, n⟩ := p n (a reindex of p along unpair.2, dropping the search
variable i); the
witness i = 0 makes the projection trivial.
A recursively decidable binary relation is recursively enumerable.
Closure properties of r.e. predicates (for Proposition 7.3 and Theorem #
7.4)
The projection-of-decidable form makes r.e.-ness closed under primitive-recursive
reindexing
(REPred.comp), conjunction (REPred.and, pairing the two search variables),
and
existential projection over ℕ (REPred.proj, absorbing the projected
variable into the search
variable). These are exactly the moves Scott's g ∘ f needs: X (g∘f) Z ↔ ∃ Y, X f Y ∧ Y g Z.
Reindexing. If p is r.e. and g is primitive recursive, then fun n => p (g n) is r.e.
(absorb g into the decidable relation along unpair.2).
Conjunction. Recursive enumerability is closed under ∧: combine the two
decidable
relations
and run the two searches in parallel (pairing the search variables i, j into a
single w).
Existential projection. If p is r.e. then so is fun n => ∃ i, p ⟨i, n⟩: fold the new
existential variable i into the search variable (pairing it with the decidable
relation's own
search variable j).
Disjunction of r.e. predicates is r.e. A witness w carries a tag w.1 ∈ {0,1} selecting
which disjunct's search index w.2 to use; the underlying relation is recursively
decidable by
RecDecidable.or/.and/.natEq. (Used for the sum-mapping f + g of Theorem
7.4.)
A choice-free primitive-recursive fold engine over Nat-coded lists #
Scott's function-space deciders (Theorem 7.5) range over finite lists of
neighborhood indices.
To stay choice-free we encode such a list as a single natural via encodeList and
process it with a
genuinely primitive-recursive foldCode. The key results are:
encodeList—List ℕ → ℕ, withl.length ≤ encodeList l(encodeList_length_le);foldCode stp p z c— folds the list coded byc, threading an accumulator and a fixed parameterp, withstpthe (coded) step function;foldCode_eq—foldCodeonencodeList lequals the correspondingList.foldl;primrec_foldCode—foldCodeis primitive recursive in all of its (primrec) inputs.
foldStep walks one entry: the state is pair remainingCode accumulator; an
empty remaining code
(= 0) is a fixed point, otherwise it pops the head and applies stp.
b ≤ pair a b (choice-free; avoids mathlib's Nat.right_le_pair to keep the
axiom set
clean).
Encode a list of naturals as a single natural: [] ↦ 0, a :: l ↦ pair a (encodeList l) + 1.
The +1 keeps the empty list (code 0) distinguishable from any nonempty list.
Equations
Instances For
The length of a list is bounded by its code; this is the fuel bound that lets
a c-fold iterate
enough times to consume the whole list coded by c.
One step of the code-walking fold. The state s = pair rc acc carries the
remaining code rc
and accumulator acc. If rc = 0 the state is a fixed point; otherwise rc - 1 = pair head tail,
and the step pops head, recurses on tail, and updates acc := stp (pair head (pair acc params)).
Equations
- One or more equations did not get rendered due to their size.
Instances For
Fold the list coded by c, threading accumulator z and parameter params.
Implemented as
c-fold iteration of foldStep from the initial state pair c z, projecting out
the accumulator.
Equations
- Domain.Recursive.foldCode stp params z c = (Nat.unpair ((Domain.Recursive.foldStep stp params)^[c] (Nat.pair c z))).2
Instances For
Core correctness of the iteration: starting from pair (encodeList l) acc,
after at least
l.length steps the accumulator equals the List.foldl of the step over l.
Correctness of foldCode. Folding the code of l equals the
corresponding List.foldl.
n.unpair.2 ≤ n (choice-free); the decreasing measure for decodeList.
Decode a natural back into a list of naturals, inverting encodeList.
Well-founded on the
remaining code (c.unpair.2 ≤ c < c + 1).
Equations
Instances For
encodeList ∘ decodeList = id: every natural is the code of its decoded list.
The decoded list is no longer than its code.
Correctness of foldCode on an arbitrary code. foldCode over any
natural c equals the
List.foldl over the list c decodes to.
foldStep (with the parameter packed into the state as pair params state)
is primitive
recursive whenever the step stp is. This is the workhorse for
primrec_foldCode.
foldCode is primitive recursive in all of its (primitive-recursive)
inputs.
Primitive-recursive exponentiation (for the 2^q subset bound) #
The funSpace consistency decider quantifies over all 2^q subsets of a
q-element step-list, so it
needs 2^q as a primitive-recursive bound. Choice-free (mathlib's Nat.Primrec
lemmas for ^ route
through classical simp).
Exponentiation is primitive recursive (unpaired (b, e) ↦ b ^ e),
choice-free.
n ↦ 2 ^ g n is primitive recursive when g is.
Halving (/2, %2) for per-subset bit extraction #
The funSpace consistency fold walks a step-list while consuming a subset bitmask
b one bit at a
time: at each entry it reads b % 2 (is this entry in the subset?) and recurses
on b / 2. Only
division/modulus by the literal 2 is needed — which omega discharges directly
— so this stays
choice-free without a general div/mod. Computed jointly by halfParity n = pair (n/2) (n%2).
halfParity n = pair (n / 2) (n % 2), built by structural recursion: from
(h, p) for n, the
value for n+1 is (h + p, 1 - p) (carry on odd→even).
Equations
- Domain.Recursive.halfParity n = Nat.rec 0 (fun (x ih : ℕ) => Nat.pair ((Nat.unpair ih).1 + (Nat.unpair ih).2) (1 - (Nat.unpair ih).2)) n
Instances For
Bounded quantifiers for recursively decidable predicates #
Scott's function-space consistency decider (Theorem 7.5) is a bounded universal
statement: a list of
step-pairs is consistent iff for every subset (coded by a bitmask b < 2^q) a
component condition
holds. Bounded quantification of a recursively decidable predicate is again
recursively decidable —
choice-free, via an explicit Nat.rec fold of the {0,1} indicator.
Indicator of v = 1, as a {0,1}-valued primitive-recursive function.
Instances For
The {0,1}-valued bounded-∀ indicator: 1 iff g (pair i n) = 1 for all
i < N. Folded
right-to-left with selectFn so the result stays in {0,1}.
Equations
- Domain.Recursive.bForallFn g n N = Nat.rec 1 (fun (i ih : ℕ) => Domain.Recursive.selectFn ih (Domain.Recursive.isOne (g (Nat.pair i n))) 0) N
Instances For
Bounded universal quantifier preserves recursive decidability. If p is
recursively
decidable
and bound is primitive recursive, then fun n => ∀ i < bound n, p (pair i n) is
recursively
decidable (choice-free).
decodeList ∘ encodeList = id (the round-trip the other way from
encodeList_decodeList).
Bounded universal quantification of an r.e. predicate over a coded list #
The "computable elements = computable maps" half of Theorem 7.5 needs r.e.-ness
closed under
∀ e ∈ decodeList c, p e for an r.e. p. Classically this is the standard
"bounded ∀ of r.e. is
r.e."; choice-free we realise the search for the finite tuple of witnesses as a
single code w
whose decoded list supplies one witness per list entry, threaded through a
foldCode.
The pure (set-theoretic) witness-threading fold step. The accumulator is pair remWitness flag:
at the list head x we pop a witness i (the decodeList head of remWitness,
i.e.
(remWitness - 1).unpair.1) with new remaining code (remWitness - 1).unpair.2,
and AND the flag
with isOne (qc ⟨i, x⟩).
Equations
- One or more equations did not get rendered due to their size.
Instances For
The {0,1}-flag computed by threading witness code w through the list coded
by c.
Equations
- Domain.Recursive.reForallChar qc w c = (Nat.unpair (Domain.Recursive.foldCode (Domain.Recursive.reForallStp qc) 0 (Nat.pair w 1) c)).2
Instances For
Core induction. Threading witness code w through l, the final flag is
1 iff the start
flag was 1 and, position by position, the k-th witness of decodeList w
satisfies qc.
Bounded ∀ over a coded list preserves recursive enumerability. If p is
r.e. then so is
fun c => ∀ e ∈ decodeList c, p e: the finite tuple of per-entry witnesses is
packed into a single
search code w, and the {0,1} flag reForallChar makes the body recursively
decidable.
Bounded ∀ over a coded list with a parameter (for curry, Theorem 7.5) #
curry's neighbourhood relation is a bounded ∀ e ∈ decodeList c, p n e whose
body depends on a
parameter n (the 𝒟₀-index) as well as the list entry e. We reduce this to
REPred.forall_mem_decodeList by primitively-recursively re-coding the list
decodeList c into the
list of pairs ⟨n, e⟩ (order is irrelevant under ∀ ∈), so the parameterised
body becomes the
plain fun s => p s.1 s.2 over the re-coded list.
Prepend the pair ⟨n, x⟩ onto the list coded by acc.
Equations
- Domain.Recursive.mapPairStep n acc x = Nat.pair (Nat.pair n x) acc + 1
Instances For
foldCode-shaped wrapper of mapPairStep (parameter n threaded via the
params slot).
Equations
- Domain.Recursive.mapPairStp w = Domain.Recursive.mapPairStep (Nat.unpair (Nat.unpair w).2).2 (Nat.unpair (Nat.unpair w).2).1 (Nat.unpair w).1
Instances For
Parameterised bounded ∀ over a coded list preserves recursive
enumerability. If p is an
r.e. binary relation then fun t => ∀ e ∈ decodeList t.2, p t.1 e is r.e.:
re-code the list into the
pairs ⟨t.1, e⟩ (mapPairCode) and apply the unparameterised
forall_mem_decodeList.