Zulip Chat Archive
Stream: lean4
Topic: Use recursor within inductive def
Marcus Rossel (Aug 09 2021 at 06:28):
Perhaps the answer to this question is very type-theoretical, but why can't I use an inductive type's recursor within its own definition?
E.g., if I try:
inductive X
| base (b : Nat)
| next (x : X) (h : x.rec (true) (λ b => false))
I get an error telling me:
invalid field notation, type is not of the form (C ...) where C is a constant
x
has type
X
Is it because that would make the recursor recursive (:drum:)? Because it would have to know about its own definition in order to be defined?
Mario Carneiro (Aug 09 2021 at 06:46):
the boring answer is that this is not allowed by the inductive definition schema
Marcus Rossel (Aug 09 2021 at 06:46):
Mario Carneiro said:
the boring answer is that this is not allowed by the inductive definition schema
Good enough for me ¯\_(ツ)_/¯
Marcus Rossel (Aug 09 2021 at 06:47):
Can this be circumvented by using an inductive family somehow?
Mario Carneiro (Aug 09 2021 at 06:47):
I don't think this is a higher inductive type - even HITs don't let you reference the recursor in the constructors because that almost certainly leads to the liar paradox somehow
Mario Carneiro (Aug 09 2021 at 06:48):
not to mention, what does the recursor's type even look like? it has to reference itself
Mario Carneiro (Aug 09 2021 at 06:49):
Maybe you could start with a description of what you want to do
Mario Carneiro (Aug 09 2021 at 06:49):
in the example, you are saying you want x
to be a base
, but in that cause the parameter could just be a Nat
Marcus Rossel (Aug 09 2021 at 06:51):
We have in fact had a discussion on this before :D.
Marcus Rossel (Aug 09 2021 at 06:54):
I want to define this cycle of mutually inductive types MutationOutput
, Mutation
, Reaction
, Reactor
, Network
.
Using Lean 4 seems to fix the issue with nested inductives.
Now my only problem is that (e.g.) Reaction
takes an instance of Mutation
in its constructor, but also should impose constraints on this instance.
Marcus Rossel (Aug 09 2021 at 06:55):
In a more MWE-syle this would be (basically):
mutual
inductive A
| zero
| next (d : D)
inductive B
| mk (a : A) (h : a.rec (false) (fun _ => true))
inductive C
| mk (b : B)
inductive D
| mk (c : C)
end
Marcus Rossel (Aug 09 2021 at 06:58):
I think I might be able to work around this though by ignoring all of these kinds of constraints at first and then defining a type (e.g.) ProperReactor
which imposes all of these constraints on its contained instances (if that makes sense).
Mario Carneiro (Aug 09 2021 at 07:00):
what kind of constraints?
Mario Carneiro (Aug 09 2021 at 07:00):
The general case of this is called an inductive-recursive type, where the constraint is a recursive function defined on the other inductive
Mario Carneiro (Aug 09 2021 at 07:01):
but if it's a simple function you might be able to write it another way
Mario Carneiro (Aug 09 2021 at 07:01):
the examples where you say that it has to be just one variant can be rephrased so that you just take the arguments to that variant instead of the inductive argument itself
Mario Carneiro (Aug 09 2021 at 07:02):
i.e.
mutual
inductive A
| zero
| next (d : D)
inductive B
| mk (d : D)
inductive C
| mk (b : B)
inductive D
| mk (c : C)
end
Mario Carneiro (Aug 09 2021 at 07:03):
if it can be multiple variants, you can still do it with a ProperA
type expressing the conjunction of A
and the constraints
Marcus Rossel (Aug 09 2021 at 07:06):
Ok the strategy for this simple example definitely makes sense. I'll try to come up with an MWE that's a bit closer to my real-world use case.
Marcus Rossel (Aug 09 2021 at 07:20):
Ok, probably not a working example, but I hope it's sufficient.
Mutation
defines a function body
that produces a MutationOutput
.
The output of this function should be constrained (outPrtValsDepOnly
) depending the mutation's own deps
.
Note, for simplicity in this example, I've expressed outPrtValsDepOnly
as if MutationOutput
were a simple structure - i.e. I haven't used (body i s).rec ...
and just wrote the (body i s).prtVals
directly.
Now a Reaction
should extend a Mutation
by further constraining the output of its body
(via noNewCns
).
mutual
inductive MutationOutput
| mk
(prtVals : List ι)
(state : Finset ι)
(newCns : List (ι × ι))
(newRcns : List (Reaction))
inductive Mutation
| mk
(deps : Finset ι)
(body : List ι → Nat → MutationOutput)
(outPrtValsDepOnly : ∀ i s {o}, (o ∉ deps) → (body i s).prtVals.nth o = none))
inductive Reaction
| mk
(core : Mutation)
(noNewCns : ∀ i s, (core.body i s).newCns = [])
end
Marcus Rossel (Aug 09 2021 at 07:25):
So basically these types should all feel like structures. But their mutual inductive nature makes it not so.
Mario Carneiro (Aug 09 2021 at 07:31):
you forgot to define or import ∉
:
constant notin : α → List α → Prop
infix:50 "∉" => notin
Marcus Rossel (Aug 09 2021 at 07:32):
Oh it could just as well be ∈
here - I've totally butchered the original definition anyway ^^
Mario Carneiro (Aug 09 2021 at 07:40):
Here's a reduction to eliminate the extra hypotheses:
mutual
inductive MutationOutput {ι : Type} : Finset ι → Type
| mk {deps}
(prtVals : List ι)
(state : Finset ι)
(newCns : List (ι × ι))
(newRcns : List Reaction)
(outPrtValsDepOnly : ∀ i s {o}, (o ∉ deps) → prtVals.nth o = none)
(noNewCns : newCns = [])
: MutationOutput deps
inductive Mutation {ι : Type} : Type
| mk
(deps : Finset ι)
(body : List ι → Nat → MutationOutput deps)
inductive Reaction {ι : Type} : Type
| mk (core : Mutation)
end
Mario Carneiro (Aug 09 2021 at 07:41):
but I think you might want to consider another approach here, which applies the well formedness conditions outside the types
Marcus Rossel (Aug 09 2021 at 08:14):
Yeah, that's what I was guessing.
Marcus Rossel (Aug 09 2021 at 08:14):
Thanks for your help!
Marcus Rossel (Aug 10 2021 at 08:11):
I'm still having problems with these damned mutual inductives :see_no_evil:
In the following example:
instance List.isSetoid (α) : Setoid (List α) := sorry
def Multiset.{u} (α : Type u) : Type u := Quotient (List.isSetoid α)
mutual
inductive Mutation (ι : Type _)
inductive Reactor (ι : Type _)
| mk (r : Multiset (Mutation ι))
end
I get the error:
(kernel) arg #2 of '_nested.Finmap_1.mk' contains a non valid occurrence of the datatypes being declared
If I define Multiset
using a sorry
instead of Quotient
, the problem disappears.
Does this mean that Lean can't handle the nested inductive in Reactor.mk
because Multiset
is a quotient type?
Eric Rodriguez (Aug 10 2021 at 08:14):
did you make the List.isSetoid
instance or is that still a sorry
? That may cause issues
Marcus Rossel (Aug 10 2021 at 08:14):
That's still a sorry.
Marcus Rossel (Aug 10 2021 at 08:17):
A fully sorry-free version has the issue too though:
inductive List.perm : List α → List α → Prop
| nil : perm [] []
| cons : ∀ (x : α) {l₁ l₂ : List α}, perm l₁ l₂ → perm (x::l₁) (x::l₂)
| swap : ∀ (x y : α) (l : List α), perm (y::x::l) (x::y::l)
| trans : ∀ {l₁ l₂ l₃ : List α}, perm l₁ l₂ → perm l₂ l₃ → perm l₁ l₃
infix:50 " ~ " => List.perm
protected theorem List.perm.refl : ∀ (l : List α), l ~ l
| [] => perm.nil
| (x::xs) => (perm.refl xs).cons x
protected theorem List.perm.symm {l₁ l₂ : List α} (p : l₁ ~ l₂) : l₂ ~ l₁ := by
induction p with
| nil => exact nil
| cons x _ p => exact cons x p
| swap x y l => exact swap y x l
| trans _ _ p₁ p₂ => exact trans p₂ p₁
theorem List.perm.eqv : Equivalence (@perm α) where
refl := perm.refl
symm := perm.symm
trans := perm.trans
instance List.isSetoid (α) : Setoid (List α) := Setoid.mk _ perm.eqv
Eric Rodriguez (Aug 10 2021 at 08:30):
so I tried doing this defn in lean3, and it even though it has the same code for dealing with positivity issues, it actually complains about universes instead -- maybe that's the real issue that's getting obfuscated?
Marcus Rossel (Aug 10 2021 at 08:36):
What do you mean by "positivity issues"?
Eric Rodriguez (Aug 10 2021 at 08:40):
the error comes from this line, which tries to check that all terms in an inductive type's constructor are "positive"
Eric Rodriguez (Aug 10 2021 at 08:40):
#tpil covers this somewhere very briefly and links to a specific paper about it (e: here)
Sebastian Ullrich (Aug 10 2021 at 08:43):
Lean implements nested inductive types by translating them to mutually inductive types, but Quotient
is not an inductive type, so this translation is not possible
Sebastian Ullrich (Aug 10 2021 at 08:45):
Basically, all types involved in the recursion must either be unfoldable or appropriate inductive types, but Quot
is a constant
Marcus Rossel (Aug 10 2021 at 09:44):
Oh no, I'll have to do some trickery then.
Thanks y'all for the help!
Last updated: Dec 20 2023 at 11:08 UTC