Zulip Chat Archive
Stream: new members
Topic: help w/ recursion termination
Dapper Mink (Aug 22 2023 at 15:54):
Hello, I'm new to lean but even newer to Zulip so sorry in advance if this shouldn't be posted here
I am playing with lean to learn how to prove termination and have this very simple example:
inductive Pic where
| rect
| pics (ps : List Pic)
def asString : Pic → String
| Pic.rect => "rect"
| Pic.pics ps =>
let strs := ps.map asString
let str := String.intercalate ", " strs
s!"pics({str})"
My asString function fails to show termination. I have read this page on the book: https://leanprover.github.io/theorem_proving_in_lean4/induction_and_recursion.html and feel like I should use WellFounded / Acc, however I also fail to define a "<" relation for Pic. Could someone tell me what is the easiest way to satisfy lean with that?
Dapper Mink (Aug 23 2023 at 17:32):
I've noticed messages are often ignored if you don't use your real name. Sorry, this is my google account but my real name is Quentin Januel. I am not a student, I am learning lean with the books and am trying to develop a game to verify my understanding. Also I am not specifically asking for a solution, a redirection towards a resource that explains this would be equally appreciated. It has been two days I am struggling with this problem because I feel like the book doesn't cover this kind of cases (lean is my first proof assistant btw).
To me, the problem is that this should be a structural recursion, but it is not probably because of the "List" indirection. I think I could maybe avoid this by using a mutual recursion with a custom list implementation, but I would like to stick with the standard list. Also I tried to implement sizeOf or a binary relation for this structure, but I feel like they all also require to show termination. Any help would be greatly appreciated.
Dapper Mink (Aug 23 2023 at 17:34):
Also the book (theorem proving) does not provide any example of "Acc" afaik
Kevin Buzzard (Aug 24 2023 at 20:36):
You're exactly right that I personally tend not to reply to people who are not using their full real name, but your question is completely reasonable as is your explanation of anonymity I guess. Another reason I didn't reply myself was that I didn't know the answer. I don't understand what s!
does because I use Lean in a totally different way to the code you're posting, but here's some code which I do understand:
inductive Pic where
| rect
| pics (ps : List Pic)
def asString : Pic → Nat
| Pic.rect => 37
| Pic.pics ps =>
let strs := ps.map asString
strs.length
and this also doesn't compile; the issue is somehow that ps.map
is taking Lean out of its comfort zone when it comes to proving that things are well-defined. We get the usual error
structural recursion cannot be used
failed to prove termination, use `termination_by` to specify a well-founded relation
but I don't know how to fix it because I only ever do induction/recursion on naturals. Just bumping this and hoping that my simpler example will encourage someone who is less mean about replying to anonymous users and more CS-ish will help you out :-)
Joachim Breitner (Aug 24 2023 at 20:49):
The problem is that lean doesn't know that ps.map
will call its argument only on pictures from ps
. But you give it a hint:
def asString : Pic → String
| Pic.rect => "rect"
| Pic.pics ps =>
let strs := ps.attach.map (fun ⟨p, _⟩ => asString p)
let str := String.intercalate ", " strs
s!"pics({str})"
See docs#List.attach for what it does (and feel free to ask more about it).
And it seems that some more magic is happening behind the scenes where lean knows that x ∈ xs
implies that x
is smaller than xs
:
https://leanprover-community.github.io/mathlib4_docs/Init/Data/List/BasicAux.html#List.tacticSizeOf_list_dec
Kevin Buzzard (Aug 24 2023 at 20:58):
I decided to try and answer this question myself but I was totally bewildered by Pic.rec
:
Pic.rec.{u} {motive_1 : Pic → Sort u} {motive_2 : List Pic → Sort u} (rect : motive_1 rect)
(pics : (ps : List Pic) → motive_2 ps → motive_1 (pics ps)) (nil : motive_2 [])
(cons : (head : Pic) → (tail : List Pic) → motive_1 head → motive_2 tail → motive_2 (head :: tail)) (t : Pic) :
motive_1 t
Why are there two motives? I understand very little about this kind of an inductive type.
Kevin Buzzard (Aug 24 2023 at 21:13):
Is this actually provable?
def asString : Pic → String
| Pic.rect => "rect"
| Pic.pics ps =>
let strs := ps.map asString
let str := String.intercalate ", " strs
s!"pics({str})"
decreasing_by {
simp
/-
a✝¹ a✝ : Pic
⊢ InvImage WellFoundedRelation.rel (fun a ↦ sizeOf a) a✝ a✝¹
-/
sorry
}
Kevin Buzzard (Aug 24 2023 at 21:18):
'induction' tactic does not support nested inductive types, the eliminator 'Pic.rec' has multiple motives
:cry:
Kevin Buzzard (Aug 24 2023 at 21:18):
I'm having trouble proving it by induction ;-)
Dapper Mink (Aug 25 2023 at 05:37):
@Joachim Breitner Thank you for your solution, it indeed works great and reading through the definitions of List.attach, List.pmap and List.forall_mem_cons made made me understand better. Also I was confused because I didn't know std4 was a thing and was wondering why my List.attach function did not exit. Fortunately, this is all sorted out now
Dapper Mink (Aug 25 2023 at 05:39):
@Kevin Buzzard Yes I'm sorry about that, I will use another google account if I ever get any more questions from now on
Last updated: Dec 20 2023 at 11:08 UTC