Zulip Chat Archive

Stream: lean4

Topic: Automated termination proof on structural-ish recursion?


Phil Nguyen (May 09 2025 at 06:18):

Suppose we define a universe of tags, and their interpretation into Lean's Types:

inductive TAG where
| NAT : TAG
| LIST : TAG  TAG

abbrev TAG.interp : TAG  Type
| NAT => Nat
| LIST t => List t.interp

Now we define an addOne function. It looks structural recursion, but is unintuitively rejected by Lean, because the second argument doesn't have a fixed type:

def addOne : (t : TAG)  t.interp  t.interp
| .NAT, n => n + 1
| .LIST _, [] => []
| .LIST t, x :: xs => addOne t x :: addOne (.LIST t) xs
termination_by t x => (t, x) -- nope

The current workaround I'm trying is explicitly defining a measure:

def TAG.sizeOf : (t : TAG)  t.interp  Nat
| NAT, _ => 0
| LIST _, xs => xs.length

def addOne : (t : TAG)  t.interp  t.interp
/- above definition -/
termination_by t x => (t, TAG.sizeOf t x)
decreasing_by
. apply Prod.Lex.left; simp
. apply Prod.Lex.right; simp [TAG.sizeOf]

Is there a more automated way to have Lean accept that addOne terminates?

Many thanks!!

Aaron Liu (May 09 2025 at 11:34):

Use a sigma type?

Phil Nguyen (May 09 2025 at 21:01):

How does that work? Could you show me?

I tried the following, but it looks like there's no default working SizeOf instance for Sigma yet:

def addOne : (t : TAG)  t.interp  t.interp
| .NAT, n => n + 1
| .LIST _, [] => []
| .LIST t, x :: xs => addOne t x :: addOne (.LIST t) xs
termination_by t x => (t, (⟨_, x : (Σ (t : TAG), t.interp)))
/-
failed to prove termination, possible solutions:
  - Use `have`-expressions to prove the remaining goals
  - Use `termination_by` to specify a different well-founded relation
  - Use `decreasing_by` to specify your own tactic for discharging this kind of goal
t : TAG
x : t.interp
xs : List t.interp
⊢ sizeOf ⟨t.LIST, xs⟩ < sizeOf ⟨t.LIST, x :: xs⟩
-/

Aaron Liu (May 09 2025 at 21:02):

I think Sigma is missing a WellFoundedRelation instance?

Robin Arnez (May 10 2025 at 20:17):

Try this:

inductive Tag where
  | nat : Tag
  | list : Tag  Tag

abbrev Tag.interp : Tag  Type
  | nat => Nat
  | list t => List t.interp

protected noncomputable def Tag.interp.sizeOf : {t : Tag}  t.interp  Nat
  | .nat, x => x
  | .list t, x => (x : List t.interp).rec 1 (fun a _ ih => 1 + a.sizeOf + ih)

noncomputable instance : SizeOf (Tag.interp t) := Tag.interp.sizeOf

@[simp] theorem Tag.interp.sizeOf_nat (x : Tag.nat.interp) : instSizeOfInterp.sizeOf x = x := rfl
@[simp] theorem Tag.interp.sizeOf_list {t : Tag} (x : t.list.interp) : instSizeOfInterp.sizeOf x = sizeOf (x : List t.interp) := rfl

def addOne (t : Tag) (x : t.interp) : t.interp :=
  match t, x with
  | .nat, n => n + 1
  | .list _, [] => []
  | .list t, x :: xs => addOne t x :: addOne (.list t) xs

It just needed the right sizeOf instance.


Last updated: Dec 20 2025 at 21:32 UTC