I've been having a bit of difficulty proving termination of a particular kind of function. I'm quite new to using Lean, so it's possible that what I'm missing is a language feature and not a technique, but here goes.
To show what I mean, consider the following type of trees.
inductive Tree (α : Type u) : Type u where | empty : Tree α | node : α → List (Tree α) → Tree α
Consider then some function that traverses a tree, like this one.
def Tree.number_of_nodes : Tree α → Nat | .empty => 0 | .node _ ts => List.foldl (fun a e => a + number_of_nodes e) 1 ts
The function is not accepted because termination cannot be proved automatically. "Structural recursion cannot be used."
My question then: what is the best approach to prove termination of functions like these, where it feels like the answer to the question "why does this function terminate" is "structural recursion, of course", but where that's obscured to the checker by the fact that the recursion is on a subterm of an immediate subterm, and not on the immediate subterm itself?
This comes up a lot whenever a type is given a definition where some container (a List, in this case) is used to hold values of the same type being defined. For example, if the type were that of binary trees, where the "intermediate" type were
Tree α × Tree α instead of
List (Tree α), the same error message would arise.
So far, I've been unable to think of a well-founded relation to give to
termination_by in these cases, so I end up writing
decreasing_by admit, but I'd like to stop doing that for obvious reasons.
P.S. I'm using the latest Lean 4 through the Code extension.
P.P.S. Any resources about techniques for showing termination in other cases are welcome as well, even if not Lean specific, but that's really for another question.