Zulip Chat Archive
Stream: general
Topic: sizeOf of an array and its toList
Juneyoung Lee (Oct 30 2025 at 18:57):
Can I prove the equality of sizeOf arr and sizeOf arr.toList? Don't know where to start. :/ How people typically find lemmas that are related to sizeOf as well as some interested function (like toList)?
theorem tmp: ∀ (x:Array Nat), sizeOf x = sizeOf x.toList := by
intro x
-- ??
Kenny Lau (Oct 30 2025 at 18:58):
I would recommend to not use sizeOf, we have List.length and Array.size
Juneyoung Lee (Oct 30 2025 at 19:47):
It seems sizeOf is for termination checking, right? I indeed need to prove termination of a recursive function.
Robin Arnez (Oct 30 2025 at 19:48):
You can also use termination_by l.length
Chris Bailey (Oct 30 2025 at 19:50):
Fwiw the statement you have is false, Array's sizeOf is 1 + x.toList._sizeOf_1. You can prove that though.
theorem tmp: ∀ (x:Array Nat), sizeOf x = 1 + (sizeOf x.toList) := by
intro x
simp [SizeOf.sizeOf, Array._sizeOf_1]
done
Juneyoung Lee (Oct 30 2025 at 19:51):
@Robin Arnez The argument was an inductive data type that has an array as its argument, something like this:
inductive MyTy where
| leaf: MyTy
| nonLeaf: Array MyTy → MyTy
def f (t:MyTy): List MyTy :=
match t with
| .leaf => []
| .nonLeaf a => List.flatten (List.map f (a.toList))
@Chris Bailey Thanks, that was the answer that I wanted. :)
Aaron Liu (Oct 30 2025 at 20:06):
In this case the "correct" thing to do (in my opinion) would be to have two mutually recursive definitions, acting on the MyTy and List MyTy respectively, and then maybe perhaps also write some csimp lemmas if those are needed.
Aaron Liu (Oct 30 2025 at 20:06):
This lets you do structural recursion instead
Juneyoung Lee (Oct 30 2025 at 20:22):
Hi Aaron, thanks for a suggestion. I tried this, but this seems to fail; do you have any idea about how to fix this?
mutual
def flist (a:List MyTy): List (List MyTy) := -- Says it failed to infer structural recursion
List.map f a
def f (t:MyTy): List MyTy :=
match t with
| .leaf => []
| .nonLeaf a => List.flatten (flist a.toList)
end
Aaron Liu (Oct 30 2025 at 20:27):
Well you can't just write List.map
Aaron Liu (Oct 30 2025 at 20:28):
you have to write out the whole thing
Aaron Liu (Oct 30 2025 at 20:28):
like with a match
Malvin Gattinger (Oct 30 2025 at 20:36):
If you want to keep the definition with map you can also define your own .length instead of sizeOf and then prove termination like this. It needs a lot of Mathlib though.
import Mathlib.Algebra.Order.BigOperators.Group.List
import Mathlib.Algebra.Order.Group.Nat
inductive MyTy where
| leaf: MyTy
| nonLeaf: List MyTy → MyTy
def MyTy.length : MyTy → Nat
| .leaf => 1
| .nonLeaf l => 1 + (l.map MyTy.length).sum
def f (t: MyTy) : List MyTy :=
match t with
| .leaf => []
| .nonLeaf a => (a.map f).flatten
-- Actually all the rest can be omitted, see Josh Cohen below ;-)
termination_by
t.length
decreasing_by
next t h =>
simp only [MyTy.length, gt_iff_lt]
have := @List.le_sum_of_mem _ _ _ _ (l.map MyTy.length) t.length (by aesop)
omega
Juneyoung Lee (Oct 30 2025 at 20:43):
Thanks Malvin. It seems if the argument of nonLeaf is List MyTy, not Array MyTy, Lean4 can smartly prove that the function f terminates. It is great to still know that when the measure is defined as t.length it can be proven like your proof.
How can we print the measure of f that Lean used for the List MyTy version? Wondering whether #synth SizeOf MyTy is the right result.
Malvin Gattinger (Oct 30 2025 at 20:46):
I have never used #synth before but it suggests MyTy._sizeOf_inst which can be shown with #reduce MyTy._sizeOf_inst :thinking:
Malvin Gattinger (Oct 30 2025 at 20:53):
Oh, and these work, suggesting that sizeOf actually does recurse into the list in a useful way?
#reduce sizeOf [MyTy.leaf] -- 3
#reduce sizeOf [(MyTy.nonLeaf [])] -- 4
#reduce sizeOf [sizeOf (MyTy.nonLeaf [MyTy.leaf])] -- 6
Juneyoung Lee (Oct 30 2025 at 20:57):
Was never aware of #reduce :), thanks. (probably it was one of the most basic commands in Lean4, but I was forgetting it; I was using eval instead)
Malvin Gattinger (Oct 30 2025 at 21:03):
No need for a custom .length actually :drum:
import Mathlib.Algebra.Order.BigOperators.Group.List
import Mathlib.Algebra.Order.Group.Nat
inductive MyTy where
| leaf: MyTy
| nonLeaf: List MyTy → MyTy
theorem helper (l : List MyTy) : (List.map sizeOf l).sum < sizeOf l := by
induction l
· simp_all
case cons t ts IH =>
simp
omega
def f (t: MyTy) : List MyTy :=
match t with
| .leaf => []
| .nonLeaf l => (l.map f).flatten
-- the rest is not needed!
decreasing_by
next t h =>
simp [gt_iff_lt]
have := @List.le_sum_of_mem _ _ _ _ (l.map sizeOf) (sizeOf t) (by aesop)
have : (l.map sizeOf).sum < sizeOf l := helper l
omega
Josh Cohen (Oct 30 2025 at 21:03):
I believe the following works as well:
inductive MyTy where
| leaf: MyTy
| nonLeaf: Array MyTy → MyTy
def f (t:MyTy): List MyTy :=
match t with
| .leaf => []
| .nonLeaf a => List.flatten (Array.map f a).toList
Malvin Gattinger (Oct 30 2025 at 21:10):
Funny, so with (a.map f).toList it can prove termination easily, but with (a.toList.map f) it cannot.
Josh Cohen (Oct 30 2025 at 21:12):
The reason this works (I believe) is here: #new members > Understanding the termination checker for nested recursion
Malvin Gattinger (Oct 30 2025 at 21:18):
Thanks! Maybe I am mixing it up now, but do I understand correctly that docs#Array.toList_map used by simp will turn (a.map f).toList into (a.toList.map f), so from an expression that the termination checker can deal with into one that it cannot deal with?
Aaron Liu (Oct 30 2025 at 21:19):
Juneyoung Lee said:
Hi Aaron, thanks for a suggestion. I tried this, but this seems to fail; do you have any idea about how to fix this?
mutual def flist (a:List MyTy): List (List MyTy) := -- Says it failed to infer structural recursion List.map f a def f (t:MyTy): List MyTy := match t with | .leaf => [] | .nonLeaf a => List.flatten (flist a.toList) end
like this
inductive MyTy where
| leaf: MyTy
| nonLeaf: Array MyTy → MyTy
mutual
def flist (a : List MyTy) : List (List MyTy) :=
match a with
| [] => []
| x :: xs => f x :: flist xs
def f (t : MyTy) : List MyTy :=
match t with
| .leaf => []
| .nonLeaf a => List.flatten (flist a.toList)
end
Mirek Olšák (Oct 30 2025 at 21:54):
Regarding sizeOf, constructors have their equations for sizeof, in case of Array
#check Array.mk.sizeOf_spec -- Array.mk.sizeOf_spec.{u} {α : Type u} [SizeOf α]
-- (toList : List α) : sizeOf { toList := toList } = 1 + sizeOf toList
So the size of an Array is exactly one more than of the appropriate List.
Mirek Olšák (Oct 30 2025 at 22:23):
That avoids unpacking the internal _size_of_1...
Juneyoung Lee (Oct 31 2025 at 01:44):
Thank you all. Every answer was very helpful. :)
Last updated: Dec 20 2025 at 21:32 UTC