Zulip Chat Archive
Stream: new members
Topic: Prove termination of mutually recursive definitions
Jakub Nowak (Oct 06 2025 at 03:33):
How can I prove termination of those mutually recursive definitions?
mutual
structure Foo : Type where
bar : Option Bar
structure Bar : Type where
foo : Foo
end
mutual
def Foo.func (foo : Foo) : Prop := foo.bar.elim True Bar.func
def Bar.func (bar : Bar) : Prop := bar.foo.func
end
Or maybe it's impossible to prove that in Lean?
On paper, these definitions terminate, because a finite term for Foo or Bar must at some point have none as a value for foo.bar. So sizeOf should be correct termination_by metric, but Lean unfortunately failed to find proof.
Jakub Nowak (Oct 06 2025 at 03:38):
The goal for Foo.func is
foo : Foo
bar : Bar
⊢ sizeOf bar < sizeOf foo
so it looks like the way I wrote this function it's impossible to prove it's terminating.
Jakub Nowak (Oct 06 2025 at 04:02):
I've used match instead of Option.elim:
mutual
structure Foo : Type where
bar : Option Bar
structure Bar : Type where
foo : Foo
end
mutual
def Foo.func (foo : Foo) : Prop :=
match eq : foo.bar with
| none => True
| some bar => bar.func
termination_by sizeOf foo
def Bar.func (bar : Bar) : Prop := bar.foo.func
termination_by sizeOf bar
end
and now the goal is
foo : Foo
bar : Bar
eq : foo.bar = some bar
⊢ sizeOf bar < sizeOf foo
which at least looks provable, but I still don't know how to prove it.
The proof should be something like sizeOf bar < sizeOf (some bar) = sizeOf foo.bar < sizeOf foo. But I don't know how to prove sizeOf foo.bar < sizeOf foo.
Jakub Nowak (Oct 06 2025 at 04:28):
Now I'm utterly confused:
mutual
structure Foo : Type where
bar : Option Bar
structure Bar : Type where
foo : Foo
end
mutual
def Foo.func (foo : Foo) : Prop :=
match eq : foo.bar with
| none => True
| some bar => bar.func
termination_by sizeOf foo
decreasing_by
calc
sizeOf bar < sizeOf (some bar) := by simp
sizeOf (some bar) = sizeOf foo.bar := by rw [eq]
sizeOf foo.bar < 1 + sizeOf foo.bar := by simp
1 + sizeOf foo.bar = sizeOf foo := by
have : foo = { bar := foo.bar } := rfl
exact (Foo.mk.sizeOf_spec foo.bar).symm
def Bar.func (bar : Bar) : Prop := bar.foo.func
termination_by sizeOf bar
end
Something (delabolator?) replaced foo = { bar := foo.bar } with foo = foo. At the same time Lean complains
Type mismatch
Eq.symm (Foo.mk.sizeOf_spec foo.bar)
has type
1 + sizeOf foo.bar = sizeOf { bar := foo.bar }
but is expected to have type
1 + sizeOf foo.bar = sizeOf foo
???
Jakub Nowak (Oct 06 2025 at 05:16):
Now I'm missing how to prove foo = Foo.mk foo.bar
mutual
structure Foo : Type where
bar : Option Bar
structure Bar : Type where
foo : Foo
end
mutual
def Foo.func (foo : Foo) : Prop :=
match eq : foo.bar with
| none => True
| some bar => bar.func
termination_by sizeOf foo
decreasing_by
calc
sizeOf bar < sizeOf (some bar) := by simp
sizeOf (some bar) = sizeOf foo.bar := by rw [eq]
sizeOf foo.bar < 1 + sizeOf foo.bar := by simp
1 + sizeOf foo.bar = sizeOf foo := by
have : foo = Foo.mk foo.bar := by sorry
rw [this]
exact (Foo.mk.sizeOf_spec foo.bar).symm
def Bar.func (bar : Bar) : Prop := bar.foo.func
termination_by sizeOf bar
end
Wang Jingting (Oct 06 2025 at 05:47):
It seems that you can tag Foo with @[ext], then you can use ext; rfl to show foo = { bar := foo.bar }.
But I think there should be a better way to do this.
Jakub Nowak (Oct 06 2025 at 05:55):
Thanks!
Now I finally have working code. But getting here was so hard and the code is so elaborate. I hope someone can point out how to make this simpler. :pleading_face:
Ideally I would want to not to have to prove termination manually at all.
mutual
@[ext]
structure Foo : Type where
bar : Option Bar
@[ext]
structure Bar : Type where
foo : Foo
end
mutual
def Foo.func (foo : Foo) : Prop :=
match eq : foo.bar with
| none => True
| some bar => bar.func
termination_by sizeOf foo
decreasing_by
simp_wf
calc
sizeOf bar < sizeOf (some bar) := by simp
sizeOf (some bar) = sizeOf foo.bar := by rw [eq]
sizeOf foo.bar < 1 + sizeOf foo.bar := by simp
1 + sizeOf foo.bar = sizeOf foo := by
have : foo = Foo.mk foo.bar := by ext; rfl
rw [this]
exact (Foo.mk.sizeOf_spec foo.bar).symm
def Bar.func (bar : Bar) : Prop := bar.foo.func
termination_by sizeOf bar
decreasing_by
simp_wf
calc
sizeOf bar.foo < 1 + sizeOf bar.foo := by simp
1 + sizeOf bar.foo = sizeOf bar := by
have : bar = Bar.mk bar.foo := by ext; rfl
rw [this]
exact (Bar.mk.sizeOf_spec bar.foo).symm
end
Jakub Nowak (Oct 06 2025 at 06:43):
Maybe the best idea is to follow this advice and not use mutual definitions in the first place:
inductive FooBarTy
| foo
| bar
inductive FooBar : FooBarTy → Type where
| mkFoo : (bar? : Option (FooBar .bar)) → FooBar .foo
| mkBar : (foo : FooBar .foo) → FooBar .bar
def FooBar.func : (foobar : FooBar ty) → Prop
| .mkFoo none => True
| .mkFoo (some bar) => bar.func
| .mkBar foo => foo.func
def Foo := FooBar .foo
def Bar := FooBar .bar
noncomputable instance : SizeOf Foo where
sizeOf foo := sizeOf (α := FooBar .foo) foo
noncomputable instance : SizeOf Bar where
sizeOf bar := sizeOf (α := FooBar .bar) bar
@[match_pattern]
def Foo.mk (bar? : Option Bar) : Foo := .mkFoo bar?
@[match_pattern]
def Bar.mk (foo : Foo) : Bar := .mkBar foo
def Foo.bar? : Foo → Option Bar
| .mkFoo bar? => bar?
def Bar.foo : Bar → Foo
| .mkBar foo => foo
@[elab_as_elim, induction_eliminator, cases_eliminator]
def Foo.rec {motive : Foo → Sort _} (mk : ∀ bar?, motive (.mk bar?)) (foo : Foo) : motive foo :=
match eq : foo with
| .mkFoo bar? =>
have : FooBar.mkFoo bar? = Foo.mk bar? := by simp [Foo.mk]
this ▸ mk bar?
@[elab_as_elim, induction_eliminator, cases_eliminator]
def Bar.rec {motive : Bar → Sort _} (mk : ∀ foo, motive (.mk foo)) (bar : Bar) : motive bar :=
match eq : bar with
| .mkBar foo =>
have : FooBar.mkBar foo = Bar.mk foo := by simp [Bar.mk]
this ▸ mk foo
def Foo.func (foo : Foo) : Prop := FooBar.func foo
def Bar.func (bar : Bar) : Prop := FooBar.func bar
Aaron Liu (Oct 06 2025 at 10:07):
By the way, this just works, and without being well-founded recursion
mutual
structure Foo : Type where
bar : Option Bar
structure Bar : Type where
foo : Foo
end
mutual
def Foo.func (foo : Foo) : Prop :=
match foo with
| .mk none => True
| .mk (some bar) => bar.func
def Bar.func (bar : Bar) : Prop :=
match bar with
| .mk foo => foo.func
end
Robin Arnez (Oct 06 2025 at 19:11):
Yeah, projections are simply not well supported in recursion
Last updated: Dec 20 2025 at 21:32 UTC