Zulip Chat Archive
Stream: lean4
Topic: brecOn application type mismatch during termination check?
Don Unagi (Apr 14 2024 at 06:21):
I'm trying to understand why this code:
inductive A : Type
| a1 : A
| a2 : A → A → A
mutual
inductive C : List A → Type
| c1 : C as
| c2 : C [] → C [a]
| c3 : C [A.a2 y x] → C [A.a2 x y]
inductive B : Type
| b : B
end
def f : C as → Unit
| C.c1 => ()
| C.c2 x => f x
| C.c3 x => f x
produces this error:
fail to show termination for
f
with errors
argument #2 was not used for structural recursion
application type mismatch
@C.brecOn (fun {as} x => Unit) as
argument
as
has type
List A : Type
but is expected to have type
B → Type : Type 1
Don Unagi (Apr 14 2024 at 06:26):
This looks obviously structurally recursive to me, and seemingly unrelated changes like removing the definition of B make the error go away, so I'm confused about what's happening
Joachim Breitner (Apr 14 2024 at 08:21):
Lean does not support structural recursion over mutual data types (yet). It seems that even an “unused” type definition in the mutual block makes it a mutual data type.
Don Unagi (Apr 14 2024 at 08:57):
It's curious that many other seemingly unrelated changes (for instance removing the c2
constructor, or changing its type from c2 : C [] → C [a]
to c2 : C [a] → C []
, or a half dozen other random things I tried) also make the error go away
Don Unagi (Apr 14 2024 at 08:59):
I guess "not supported" could mean "works unpredictably" but I wonder if there's a heuristic for when it does and doesn't work?
Mario Carneiro (Apr 14 2024 at 08:59):
it will use well founded induction on sizeOf
, so if there is something else which is decreasing then it may do the proof over that instead
Mario Carneiro (Apr 14 2024 at 09:05):
Note that your original example works when written as
def f : C as → Unit
| C.c1 => ()
| C.c2 x => f x
| C.c3 x => f x
termination_by x => sizeOf x
It appears that lean is trying to do induction on as
instead of x
, resulting in the unprovable goal sizeOf [A.a2 y x] < sizeOf [A.a2 x y]
Mario Carneiro (Apr 14 2024 at 09:06):
this seems like a bug in GuessLex @Joachim Breitner
Joachim Breitner (Apr 14 2024 at 09:57):
If that's the case, a bug report is very much appreciated
Joachim Breitner (Apr 14 2024 at 17:32):
Had a look. This also works:
def f : C as → Unit
| C.c1 => ()
| C.c2 x => f x
| C.c3 x => f x
-- termination_by x2 => (sizeOf as, sizeOf x2)
decreasing_by all_goals simp_wf; omega
Here is what happens: GuessLex looks at how as
and x2
decrease in each call, and finds out that (as, x2)
is a suitable termination measure. In the third case, as
decreases non-strictly (≤) and x2
decreases strictly (<). Because GuessLex looks at how each of these behave separately, this looks promising, and it sets
termination_by x2 => (sizeOf as, sizeOf x2)
But then the default decreasing_tactic
is not able to make use sense of this, because
repeat (first | apply Prod.Lex.right | apply Prod.Lex.left)
can only handle (<, ?)
and (=, <)
, (with =
being defeq), but not (≤, <)
. When I wrote GuessLex I was hoping to improve the decreasing_tactic
behavior along it, but revamping that did not happen yet.
Incidentially, omega has some (incomplete) support for Prod.Lex, that’s why simp_wf; omega
works.
Probably GuessLex should not even look for ≤
, at least not until the default termination tactic can handle that, as unfortunate as that is.
Joachim Breitner (Apr 14 2024 at 17:38):
Noted as https://github.com/leanprover/lean4/issues/3906.
Last updated: May 02 2025 at 03:31 UTC