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 structure
s.
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