Zulip Chat Archive

Stream: lean4

Topic: Struggling with termination over "nested" recursion


Valentin Robert (Jul 18 2025 at 00:09):

I am trying to write a recursive function over a datatype that contains a list of itself. I'm almost there, but I can't get my proof done:

inductive KVTree (K V : Type) where
  | KVLeaf : Bool  KVTree K V
  | KVNode : List (K × KVTree K V)  KVTree K V  KVTree K V

def sizeOfListElement (l : List T) (e : T) : sizeOf e < sizeOf l := by
  simp
  cases l
  simp
  simp
  apply Nat.lt_add_right
  simp

def interpretKVTree [BEq K] : KVTree K V  K  Bool
  | .KVLeaf b => fun _ => b
  | .KVNode kvs default => fun k =>
    match H : List.find? (fun (k', _) => k == k') kvs with
    | none => interpretKVTree default k
    | some (fst, v) => interpretKVTree v k
termination_by kv => sizeOf kv
decreasing_by

  simp
  apply Nat.lt_add_right
  trivial

  simp
  apply Nat.lt_add_right
  apply Nat.lt_add_left
  apply Nat.lt_trans (m := sizeOf (fst, v))
  simp
  apply sizeOfListElement (e := (fst, v)) (l := kvs)

The error:

tactic 'apply' failed, could not unify the type of `sizeOfListElement kvs (fst, v)`
  @sizeOf (K × KVTree K V) (instSizeOfDefault (K × KVTree K V)) (fst, v) < sizeOf kvs
with the goal
  @sizeOf (K × KVTree K V) (Prod._sizeOf_inst K (KVTree K V)) (fst, v) < sizeOf kvs

suggests that the problem is the inference of the SizeOf instance picks a different instance for my current theorem (instSizeOfDefault (K × KVTree K V)) versus when I invoke my sizeOfListElement lemma (Prod.sizeOf_inst K (KVTree K V)).

How would I go about forcing the two to use the same instance?

Or alternatively, is there a way to get my function working with much less work on my part?

Joachim Breitner (Jul 18 2025 at 07:13):

This default SizeOf instance certainly is a footgun. I wonder what breaks if we do without.

Anyways, your sozeOfListElement looks fishy: the size of any element is less than the size of any list, even totally unrelated ones?

Robin Arnez (Jul 18 2025 at 07:18):

You forgot a [SizeOf T] assumption is sizeOfListElement. Otherwise, you can do:

def interpretKVTree [BEq K] : KVTree K V  K  Bool
  | .KVLeaf b => fun _ => b
  | .KVNode kvs default => fun k =>
    match _ : List.find? (fun (k', _) => k == k') kvs with
    | none => interpretKVTree default k
    | some (_, v) => interpretKVTree v k
termination_by kv => sizeOf kv
decreasing_by all_goals grind [ List.sizeOf_lt_of_mem, = Prod.mk.sizeOf_spec, = KVTree.KVNode.sizeOf_spec]

Robin Arnez (Jul 18 2025 at 07:19):

(List.sizeOf_lt_of_mem is your sizeOfListElement)


Last updated: Dec 20 2025 at 21:32 UTC