Zulip Chat Archive
Stream: new members
Topic: First experiments with recursive functions
Madir Mabbott (Apr 05 2024 at 12:28):
My first recursive functions were so familiar and direct (addition, Fibonacci), no explicit termination_by
hints were necessary. Now I'm having problems when the function structure gets more complicated. Even as little as wrapping the function parameter in a structure foils me.
/-Obvious termination :: Iterate down a list, discarding terms, until we hit the empty string -/
def recurse_to_empty1 (lst : List String) : List String :=
match lst with
| [] => []
| _ :: tl => recurse_to_empty1 tl
termination_by sizeOf lst -- termination is automatically determined, but can be made explicit
#eval recurse_to_empty1 [ "a", "b", "c"] : List String
-- [] -- as proven, the evaluation terminates
-- Now wrap the solitary parameter in a structure
structure wrapper where lst : List String
def wrappedLst : wrapper := { lst := [ "a", "b", "c"] }
/-- Unclear termination :: The same recursion as before, but now the input parameter is wrapped in a structure -/
def recurse_to_empty2 (w : wrapper) : List String :=
match w.lst with
| [] => []
| _ :: tl => recurse_to_empty2 { lst := tl : wrapper}
termination_by sorry -- variants attempted, w, w.lst, sizeOf w, sizeOf w.lst, List.length w.lst
#eval recurse_to_empty2 wrappedLst
-- [] -- nevertheless, it does terminate
Can anyone help?
Mitchell Lee (Apr 07 2024 at 04:13):
Here's what the termination goal for your recurse_to_empty2
looks like if you use termination_by w.lst.length
.
w : wrapper
tl : List String
⊢ List.length tl < List.length w.lst
Of course, this is not provable. Essentially, there is a missing hypothesis like w.lst = head✝ :: tl
. Unfortunately, it seems that Lean does not "remember" this fact.
Here's one way to fix this.
def recurse_to_empty3 (w : wrapper) : List String :=
match w with
| { lst := [] } => []
| { lst := _ :: tl } => recurse_to_empty3 { lst := tl }
Or, a bit cleaner:
def recurse_to_empty4 (w : wrapper) : List String :=
match w with
| ⟨[]⟩ => []
| ⟨_ :: tl⟩ => recurse_to_empty4 ⟨tl⟩
Sorry if this isn't the most satisfactory answer.
Madir Mabbott (Apr 07 2024 at 11:43):
Many thanks. I will study this closely.
Marcus Rossel (Apr 07 2024 at 12:05):
Note that you can make Lean remember what w
has matched against (by adding the h :
):
def recurse_to_empty2 (w : wrapper) : List String :=
match h : w.lst with
| [] => []
| _ :: tl => recurse_to_empty2 { lst := tl : wrapper}
termination_by w.lst.length
decreasing_by
simp_wf
simp [h]
Last updated: May 02 2025 at 03:31 UTC