Zulip Chat Archive
Stream: general
Topic: Proving termination with List.Shortlex
Mirek Olšák (Oct 30 2025 at 19:41):
What is the right way to use List.Shortlex to prove termination? Let's consider this demo function (I don't care this is just fold in a way, I want to know the proper way to use Shortlex)
def f2 (l : List ℕ) : ℕ := match l with
| [] => 0
| [x] => x
| 0::tail => f2 tail
| (k+1)::n::tail => f2 (k::(2 ^ (n+k))::tail)
Then I was able to do it this way
local instance : WellFoundedRelation (List ℕ) where
rel a b := List.Shortlex (· < ·) a b
wf := List.Shortlex.wf (inferInstance : WellFoundedRelation ℕ).wf
def f2 (l : List ℕ) : ℕ := match l with
| [] => 0
| [x] => x
| 0::tail => f2 tail
| (k+1)::n::tail => f2 (k::(2 ^ (n+k))::tail)
termination_by l
decreasing_by
· decreasing_tactic
· simp [List.shortlex_def]
apply List.Lex.rel
simp
But it feels a bit clumsy.
- Is there a faster way to get
WellFoundedRelation (List ℕ)? I would expect a simple conversion, once there isList.Shortlex.wf - Do I have to declare it as an instance? Ideally, I would like to just tell
termination_byto use this relation.
Malvin Gattinger (Oct 30 2025 at 19:47):
I had a similar case where docs#WellFounded.wrap could be used to avoid declaring an instance, maybe that works here too?
Malvin Gattinger (Oct 30 2025 at 19:53):
Yes!
import Mathlib
def f2 (l : List ℕ) : ℕ := match l with
| [] => 0
| [x] => x
| 0::tail => f2 tail
| (k+1)::n::tail => f2 (k::(2 ^ (n+k))::tail)
termination_by
WellFounded.wrap (List.Shortlex.wf (Nat.lt_wfRel).2) l
decreasing_by
· decreasing_tactic
· simp [List.shortlex_def]
apply List.Lex.rel
simp
Mirek Olšák (Oct 30 2025 at 19:57):
Thanks! On my computer, I had to explicitly unwrap simp [WellFounded.wrap], but that might be just an old Lean version...
Malvin Gattinger (Oct 30 2025 at 19:57):
Ah, I made the above on live.lean-lang.org
Mirek Olšák (Oct 30 2025 at 19:58):
Looks much better, also the List.Shortlex.wf Nat.lt_wfRel.2. I was somehow not able to click through to get the core well foundedness on naturals.
Malvin Gattinger (Oct 30 2025 at 19:58):
Yes, I also would have guessed there should be a Nat.lt_wf (without Rel) :thinking:
Aaron Liu (Oct 30 2025 at 20:03):
Mirek Olšák said:
Thanks! On my computer, I had to explicitly unwrap
simp [WellFounded.wrap], but that might be just an old Lean version...
That was batteries#1308
Mirek Olšák (Oct 30 2025 at 20:04):
Malvin Gattinger said:
Yes, I also would have guessed there should be a
Nat.lt_wf(withoutRel) :thinking:
Although if it was not Nat, getting from an existing WellFoundedRelation typeclass instance to a WellFounded assumption still feels a little more complicated than it should be...
Malvin Gattinger (Oct 30 2025 at 20:13):
I agree this feels overkill. It bothered me before that termination_by allows us to specify the value (and thus its type) that should be decreasing, but the choice which relation on that type is used has to be done via class inference or with wrap. Some months ago when discussing with @Andrés Goens and @Haitian Wang I was dreaming of a way to prove termination without going via a WellFoundedRelation, something like this (NOT working Lean code):
def α : Type := sorry
def g : α → α := sorry
def f : (x : α) → α
| x => f (g x)
termination_by
x -- fixes value and type, but not yet the relation
with_relation
fun x y => sorry -- define custom relation here
is_wf_by
-- GOAL: WellFounded (fun x y => sorry)
sorry
decreasing_by
-- GOAL: (fun x y => sorry) (g x) x
sorry
In particular this would be useful if this termination proof is the only place where that relation and its wellfoundedness are needed. (e.g.)
Joachim Breitner (Oct 30 2025 at 20:23):
That’s plausible, although I’m not sure if this is a common enough use case to justify expanding the syntax surface, when wrap is a decent escape hatch for advanced users.
Mirek Olšák (Oct 30 2025 at 20:24):
To me, it would also feel a bit more principled, perhaps something like
with_relation
rel a b := ...
is_wf := ... -- optional, with some default inference
Mirek Olšák (Oct 30 2025 at 20:24):
But sure, I am fine with the wrap hack for now...
Joachim Breitner (Oct 30 2025 at 20:25):
The whole post-function-syntax (with termination_by, decreasing_by, partial_fixpoint etc) is suffering from organic growth already…
Mirek Olšák (Oct 30 2025 at 20:28):
Sure, no problem. Maybe there is a more principled termination syntax in general...
Malvin Gattinger (Oct 30 2025 at 20:29):
Agreed! I am not suggesting/demanding that this should be done, I just wanted to mention that I thought of it before. This was right before I discovered wrap which is a good enough tool.
Mirek Olšák (Oct 30 2025 at 20:40):
I could imagine something like.
def f : (x : α) → α
| x => f (g x)
termination_by
key := x
rel := LT.lt
is_wf := inferWF
proof := by decreasing_tactic
almost copying a syntax of a structure with default fields, only the key would be a bit of a hack, and keeping a shortcut when only key is presented. On the other hand, I agree refactoring this is low priority.
Last updated: Dec 20 2025 at 21:32 UTC