Zulip Chat Archive
Stream: lean4
Topic: self reference in inductive
Paige Thomas (Sep 10 2024 at 05:06):
I recall being told at some point in Lean 3 that something like this doesn't work:
inductive LabeledTree (α : Type)
| mk (root : Option α) (descendent_list : List (LabeledTree α)) : LabeledTree α
because the type being defined is referenced in another inductive type, the List. I was wondering if this is still the case. Lean 4 accepted this definition, but I'm not sure what issues I may end up running into.
Paige Thomas (Sep 10 2024 at 05:36):
Also, if I want to refer to the items in the constructor, is this the canonical way to do so?
inductive LabeledTree (α : Type)
| mk (root : Option α) (descendent_list : List (LabeledTree α)) : LabeledTree α
compile_inductive% LabeledTree
def LabeledTree.root
{α : Type} :
LabeledTree α → Option α
| mk root _ => root
def LabeledTree.descendent_list
{α : Type} :
LabeledTree α → List (LabeledTree α)
| mk _ descendent_list => descendent_list
Paige Thomas (Sep 10 2024 at 05:36):
Or is there another way to make it builtin, like with the dot notation of structures.
Kyle Miller (Sep 10 2024 at 05:40):
There's no built-in way to generate accessor functions at the moment (though it occurred to me that I have a not-so-tested-one lying around: https://gist.github.com/kmill/f83401e20bc9e36624086c8eceb5b157)
Kyle Miller (Sep 10 2024 at 05:41):
You shouldn't need compile_inductive% unless you're using LabeledTree.rec directly to make definitions.
Paige Thomas (Sep 10 2024 at 05:42):
I see. Thank you.
Paige Thomas (Sep 10 2024 at 05:43):
Is it possible to use the self reference like that now, or is this still to be avoided?
Kyle Miller (Sep 10 2024 at 05:45):
My understanding is that nested inductive types are supported by the kernel now. I haven't noticed any issues with them myself.
Kyle Miller (Sep 10 2024 at 05:52):
A case study is that this recursive function on your tree appears to work fine and have reasonable equation lemmas:
def LabeledTree.sum {α : Type} [Add α] [Zero α] : LabeledTree α → α
| mk root [] => root.getD 0
| mk root (child :: descendent_list) => child.sum + LabeledTree.sum (mk root descendent_list)
#check LabeledTree.sum.eq_1
/-
LabeledTree.sum.eq_1 {α : Type} [Add α] [Zero α] (root : Option α) :
(LabeledTree.mk root []).sum = root.getD 0
-/
#check LabeledTree.sum.eq_2
/-
LabeledTree.sum.eq_2 {α : Type} [Add α] [Zero α] (root : Option α)
(child : LabeledTree α) (descendent_list : List (LabeledTree α)) :
(LabeledTree.mk root (child :: descendent_list)).sum = child.sum + (LabeledTree.mk root descendent_list).sum
-/
Paige Thomas (Sep 10 2024 at 05:55):
What is an equation lemma?
Paige Thomas (Sep 10 2024 at 06:18):
Is this an issue due to the self reference in the List?
'induction' tactic does not support nested inductive types, the eliminator 'LabeledTree.rec' has multiple motives
Paige Thomas (Sep 10 2024 at 06:20):
cases works here, but induction gives the error.
instance (α : Type) (T : LabeledTree α) : Decidable T.isLeaf :=
by
cases T
simp only [LabeledTree.isLeaf]
infer_instance
Paige Thomas (Sep 10 2024 at 06:21):
Or maybe it is because there is only one constructor and no base constructor?
Paige Thomas (Sep 10 2024 at 06:24):
Hmm, no, it doesn't work with this either
inductive LabeledTree (α : Type)
| nil : LabeledTree α
| mk : Option α → List (LabeledTree α) → LabeledTree α
Joachim Breitner (Sep 10 2024 at 06:28):
Kayla Thomas said:
Is this an issue due to the self reference in the
List?
'induction' tactic does not support nested inductive types, the eliminator 'LabeledTree.rec' has multiple motives
Yes. A nested inductive data type will have a recursor with one motive for LabeledTree and one for List LabeledTree. Your options are, I think,
- Define your theorem recursively
-
Instantiate the second inductive motive explicitly
induction T using LabeledTree.rec (motive2 := fun TS => … TS …) -
Don’t use tactics, but write
:= LabeledTree.rec (motive1 := …) (motive2 := …) …explicitly.
Probably the first one is simplest.
Paige Thomas (Sep 10 2024 at 06:34):
I see. What goes in the ...s?
Edward van de Meent (Sep 10 2024 at 06:35):
- use
match T with<- i recommend this one
Edward van de Meent (Sep 10 2024 at 06:36):
possibly use a mutual block
Paige Thomas (Sep 10 2024 at 06:36):
That won't be recursive on the structure of T will it?
Edward van de Meent (Sep 10 2024 at 06:36):
it will if you refer to the statement in the statement.
Paige Thomas (Sep 10 2024 at 06:37):
Oh, you are referring to the first one that Joachim listed?
Edward van de Meent (Sep 10 2024 at 06:37):
ah, yes. my bad.
Paige Thomas (Sep 10 2024 at 06:37):
I see.
Paige Thomas (Sep 10 2024 at 06:38):
No problem.
Paige Thomas (Sep 10 2024 at 06:39):
Is there any chance it is possible to make this work more simply with the induction tactic at some point in the future?
Floris van Doorn (Sep 10 2024 at 08:05):
Note that one thing that doesn't work well with these nested inductive types is to use List-operations in recursive definitions. For example, the following is reasonable to write, but Lean doesn't support it (yet?):
import Mathlib.Algebra.BigOperators.Group.List
inductive LabeledTree (α : Type) where
| mk (root : Option α) (descendents : List (LabeledTree α)) : LabeledTree α
def LabeledTree.sum {α : Type} [Add α] [Zero α] : LabeledTree α → α
| mk root [] => root.getD 0
| mk root l => l.map LabeledTree.sum |>.sum
Jakob von Raumer (Sep 10 2024 at 13:33):
Would be cool if that would work some day. I have a lot of metaprogramming stuff that's recursive in that way and needs to be made partial right now
Daniel Weber (Sep 10 2024 at 14:05):
You can show that this terminates by mapping on l.attach
Last updated: May 02 2025 at 03:31 UTC