Zulip Chat Archive

Stream: lean4

Topic: Extrinsic termination proofs for well-founded recursion


Joachim Breitner (Mar 11 2025 at 09:08):

I wrote a blog post about who well-founded recursion proofs could possibly also be done in Lean. This is a dead-end (hence exiled to my blog), but at least I found it interesting to ponder this:
https://www.joachim-breitner.de/blog/816-Extrinsic_termination_proofs_for_well-founded_recursion_in_Lean

Robin Arnez (Mar 12 2025 at 20:02):

I actually thought of a very similar concept but instead with the idea of not providing the proof directly in the declaration, so basically partial but you can still use it in proofs. I used something like this:

open scoped Classical

inductive Terminates {α : Sort u} {β : α  Sort v}
    (f : ((x : α)  β x)  ((x : α)  β x)) : (x : α)  β x  Prop where
  | intro (x : α) (y : β x) (p : α  Prop) (g : (y : α)  p y  β y)
    (h :  y (h : p y), Terminates f y (g y h))
    (h2 :  g' : (x : α)  β x, f (fun z => if h : p z then g z h else g' z) x = y) : Terminates f x y

@[specialize]
partial def computableFix {α : Sort u} {β : α  Sort v} [(x : α)  Nonempty (β x)]
    (f : ((x : α)  β x)  ((x : α)  β x)) : (x : α)  β x :=
  f (computableFix f)

@[implemented_by computableFix]
noncomputable def fix {α : Sort u} {β : α  Sort v} [(x : α)  Nonempty (β x)]
    (f : ((x : α)  β x)  ((x : α)  β x)) : (x : α)  β x := by
  intro x
  by_cases h :  y, terminates f x y
  · exact h.choose
  · exact computableFix f x

That basically means that the functions doesn't need to be well-founded entirely, it can also have points that don't terminate but those are still opaque. I also had my own definition of callsOn which used ifs and yours is definitely much cleaner lol.

Joachim Breitner (Mar 17 2025 at 09:00):

Indeed, that’s neat. Note that this variant only works for types that inhabited a priori, which is a bit more restrictive.

Have you tried replacing the implemented_by with a computableFix?

@[csimp]
theorem fix_eq_computableFix : @fix = @computableFix :=by
  funext α β _ f x
  unfold fix
  split
  next h =>
    obtain y, hy := h
    induction hy with
    | intro x y p g ht heq ih =>
      sorry
  next =>
    rfl

Or, in other words, are you able to prove a fixedpiont equation for your fix, if it is terminating?

Robin Arnez (Mar 17 2025 at 13:38):

This was my original proof:

theorem Terminates.unique {α : Sort u} {β : α  Sort v} [(x : α)  Nonempty (β x)]
    (f : ((x : α)  β x)  ((x : α)  β x)) (x : α) (y1 : β x) (y2 : β x)
    (h1 : Terminates f x y1) (h2 : Terminates f x y2) : y1 = y2 := by
  induction h1 with
  | intro x y1 p1 g1 h1 h1' ih =>
    have ⟨_, _, p2, g2, h2, h2' := h2
    specialize h1' (fun z => if h : p2 z then g2 z h else Classical.ofNonempty)
    specialize h2' (fun z => if h : p1 z then g1 z h else Classical.ofNonempty)
    have : (fun z => if h : p1 z then g1 z h else if h : p2 z then g2 z h else Classical.ofNonempty)
        = (fun z => if h : p2 z then g2 z h else if h : p1 z then g1 z h else Classical.ofNonempty) := by
      ext z
      split
      · split
        · exact ih z _ _ (h2 z ‹_›)
        · rfl
      · rfl
    rw [this] at h1'
    rw [h1'] at h2'
    exact h2'

theorem fix_eq {α : Sort u} {β : α  Sort v} [(x : α)  Nonempty (β x)]
    (f : ((x : α)  β x)  ((x : α)  β x)) (x : α) (h :  y, Terminates f x y)
    : fix f x = f (fix f) x := by
  have y, _, _, p, g, h1, h2 := h
  have hterm : Terminates f x y := ⟨_, _, p, g, h1, h2
  specialize h2 (fix f)
  conv => lhs; unfold fix
  simp [h]
  have : (fun z => if h : p z then g z h else fix f z) = fix f := by
    ext z
    unfold fix
    split
    · have :  y, Terminates f z y := ⟨_, (h1 z ‹_›)
      simp [this]
      apply Terminates.unique
      · exact h1 z ‹_›
      · exact this.choose_spec
    · rfl
  rw [this] at h2
  rw [h2]
  apply Terminates.unique
  · exact h.choose_spec
  · exact hterm

Robin Arnez (Mar 18 2025 at 19:43):

I actually worked on it a bit more and made this work:

def log2Example : Nat  Nat :=
  Partial.fix (fun f x => if x  1 then 0 else f (x / 2) + 1)

def log2Example' : (Nat  Nat) × (Nat  Nat) :=
  Partial.fix (fun (f, g) => (
    fun x => if x  1 then 0 else g (x / 2) + 1, -- f
    fun x => if x  1 then 0 else f (x / 2) + 1  -- g
  ))

=> very simple declaration of normally and mutually recursive functions

Full code

Robin Arnez (Mar 18 2025 at 20:12):

The proof for it being equivalent to Nat.log2 is possible, although a bit cumbersome:

example : log2Example = Nat.log2 := by
  unfold log2Example
  have :  x, Partial.fix.dom (fun f x => if x  1 then 0 else f (x / 2) + 1) x := by
    intro x, ()
    simp only [Partial.fix.dom, Partial.toFun, Partial.ofFun]
    fun_induction Nat.log2 x with
    | case1 x hx ih =>
      apply Terminates.intro
      apply CallsOn.of_forall_ite_eq (fun f g => ?_)
      simp only [ih, reduceIte]
    | case2 x hx =>
      apply Terminates.intro
      apply CallsOn.of_forall_ite_eq (fun f g => ?_)
      have : x  1 := by omega
      simp [this]
  rw [Partial.fix_eq this]
  funext x
  fun_induction Nat.log2 x with
  | case1 x hx ih =>
    rw [Partial.fix_eq this]
    have : ¬x  1 := by omega
    conv => rhs; unfold Nat.log2
    simp only [ih, this, reduceIte, hx]
  | case2 x hx =>
    have : x  1 := by omega
    conv => rhs; unfold Nat.log2
    simp only [this, reduceIte, hx]

Last updated: May 02 2025 at 03:31 UTC