Zulip Chat Archive
Stream: lean4
Topic: Inductive datatype definition failure
bblaxill (May 10 2023 at 16:25):
Hi, I'm trying to port some existing work from agda by someone else, as an exercise. I'm currently bumping into an issue with an inductive datatype definition that I believe should pass but does not (maybe I'm wrong?). It can be boiled down to this following example, where Foo1 and Foo2 pass, but Foo3 does not.
inductive Foo1 : Nat -> Nat -> Type where
| bar1 : ∀ (a b: Nat), (Foo1 a b × Foo1 a b) -> Foo1 a b
inductive Foo2 : Nat -> Nat -> Type where
| bar2 : ∀ (a b c: Nat), (Foo2 a b × Foo2 a b) -> Foo2 a b
-- The following fails with:
-- (kernel) invalid nested inductive datatype 'Prod', nested inductive datatypes parameters cannot contain local variables.
inductive Foo3 : Nat -> Nat -> Type where
| bar3 : ∀ (a b c: Nat), (Foo3 a c × Foo3 b c) -> Foo3 a c
Perhaps I am doing something trivially wrong and should not expect this to be accepted?
I posted an issue
Andrés Goens (May 10 2023 at 23:08):
why do you need this to be a product? if you write it as an arrow it seems to work (i.e. this works for me):
inductive Foo3 : Nat -> Nat -> Type where
| bar3 : ∀ (a b c: Nat), Foo3 a c -> Foo3 b c -> Foo3 a c
bblaxill (Jun 09 2023 at 15:17):
Unpacking is not possible in my real use case, the type is completely abstract.
Here is an example closer to my use case
def implication : (List Ty -> Type _) → (List Ty -> Type _) → (List Ty -> Type _)
| P , Q => fun x => P x → Q x
infix:50 " ⇒ " => implication
inductive Ty where
| unit : Ty
| lollipop : Ty -> Ty -> Ty
infix:50 " ⊸ " => Ty.lollipop
def ternaryRelate (a b c : List Ty): Prop := a ++ b = c
notation a " • " b " ≃ " c => ternaryRelate a b c
structure ProofRelevantSeparation
(P Q : List Ty -> Type _) (a : List Ty) where
al : A
ar : A
px : P a
qx : Q a
proof : al • ar ≃ a
notation a " ★ " b => ProofRelevantSeparation a b
-- The following fails with:
-- (kernel) invalid nested inductive datatype 'ProofRelevantSeparation', nested inductive datatypes parameters cannot contain local variables.
inductive Exp : Ty -> List Ty -> Type _ where
| app : forall x, (Exp (a ⊸ b) ★ Exp a ⇒ Exp b) x -> Exp b ctx
Here ProofRelevantSeparation
is quantified over with 'x' and so cannot be unpacked (even if it could, my actual use case is more abstract)
Mario Carneiro (Jun 09 2023 at 16:26):
Lean only accepts nested inductives if it is sufficiently concrete that it could be unfolded, and it does the unfolding behind the scenes because it needs to use it to generate the recursor. Using an abstract type is definitely not allowed, and is also inconsistent in some cases
Mario Carneiro (Jun 09 2023 at 16:31):
I think there is a typo in your definition, A
in the types of al
and ar
is not defined, maybe you meant List Ty
?
Mario Carneiro (Jun 09 2023 at 16:33):
Even after inlining everything, I get this:
inductive Exp : Ty → List Ty → Type _ where
| app : forall x,
(al : List Ty) →
(ar : List Ty) →
(proof : al • ar ≃ x) →
(px : Exp (a ⊸ b) x) →
(qx : Exp a x → Exp b x) →
Exp b ctx
which is also invalid because Exp
is not used strictly positively
bblaxill (Jun 09 2023 at 16:34):
Thanks for the response.
Mario Carneiro said:
I think there is a typo in your definition,
A
in the types ofal
andar
is not defined, maybe you meantList Ty
?
Yes you are right that is a typo, but the error is the same.
bblaxill (Jun 09 2023 at 16:43):
Mario Carneiro said:
Even after inlining everything, I get this:
inductive Exp : Ty → List Ty → Type _ where | app : forall x, (al : List Ty) → (ar : List Ty) → (proof : al • ar ≃ x) → (px : Exp (a ⊸ b) x) → (qx : Exp a x → Exp b x) → Exp b ctx
which is also invalid because
Exp
is not used strictly positively
Ah you're right, although perhaps a separate issue. I'm trying to port some Agda code, do you know if Agda has greater leniency regarding nested inductives? I've seen Agda code being more lenient when constructing fixpoint of inductives even when they are not strictly positive.
Mario Carneiro (Jun 09 2023 at 16:48):
Violating strict positivity is definitely a no-no. Currently you can prove that Exp
is empty, but if you added enough constructors such that it could be proved to have at least two elements:
inductive Exp : Ty → List Ty → Type _ where
| app : forall x,
(px : Exp (a ⊸ b) x) →
(qx : Exp a x → Exp b x) →
Exp b ctx
| zero : Exp a x
| one : Exp a x
you get something which is definitely inconsistent, because (T -> T) injects into T
bblaxill (Jun 09 2023 at 16:53):
I'm not sure that inlining is correct, or perhaps my notation is incorrect. The ProofRelevantSeparation
should be on Exp (a ⊸ b)
and Exp a x
, Exp b x
being after the implication arrow
Mario Carneiro (Jun 09 2023 at 16:54):
import Mathlib.Logic.Function.Basic
import Mathlib.Tactic.Classical
def implication : (List Ty -> Type _) → (List Ty -> Type _) → (List Ty -> Type _)
| P , Q => fun x => P x → Q x
infix:50 " ⇒ " => implication
inductive Ty where
| unit : Ty
| lollipop : Ty -> Ty -> Ty
infix:50 " ⊸ " => Ty.lollipop
unsafe inductive Exp : Ty → List Ty → Type _ where
| app : forall x,
(px : Exp (a ⊸ b) x) →
(qx : Exp a x → Exp b x) →
Exp b ctx
| zero : Exp a x
| one : Exp a x
unsafe example : False := by
classical
let F (f : Exp Ty.unit [] → Exp Ty.unit []) : Exp Ty.unit [] :=
.app _ .zero f
have F_inj {x y} (h : F x = F y) : x = y := by cases h; rfl
let G (p : Prop) : Exp Ty.unit [] := if p then .zero else .one
have G_inj {x y} : G x = G y → x = y := by simp; split <;> split <;> simp_all
apply Function.cantor_injective fun P => F (G ∘ P)
intro P Q h; funext a; exact G_inj (congrFun (F_inj h) a)
Mario Carneiro (Jun 09 2023 at 16:56):
I'm fairly sure the inlining is correct, but it's possible there were issues in the original statement?
Mario Carneiro (Jun 09 2023 at 16:57):
I think maybe you might have meant
inductive Exp : Ty -> List Ty -> Type _ where
| app : forall x, (Exp (a ⊸ b) ★ Exp a) x -> Exp b ctx
if the intent is to act like function application
Mario Carneiro (Jun 09 2023 at 16:58):
If I make that modification I get
inductive Exp : Ty → List Ty → Type _ where
| app : forall x,
(al : List Ty) →
(ar : List Ty) →
(proof : al • ar ≃ x) →
(px : Exp (a ⊸ b) x) →
(qx : Exp a x) →
Exp b ctx
for the inlined form, and this does satisfy strict positivity and is accepted by lean
Mario Carneiro (Jun 09 2023 at 17:00):
(it still looks wrong because the ctx
at the end is not used anywhere else, but possibly this is an artifact of minimizing)
bblaxill (Jun 09 2023 at 17:11):
Mario Carneiro said:
I think maybe you might have meant
inductive Exp : Ty -> List Ty -> Type _ where | app : forall x, (Exp (a ⊸ b) ★ Exp a) x -> Exp b ctx
if the intent is to act like function application
Thanks, I think you have solved my problem. My translation from the Agda was not correct.
I'm curious about the original Foo3
tuple example- I would have expected Foo2
to also fail, but I admit I have no intuition for what is happening.
James Gallicchio (Jun 09 2023 at 19:10):
I'm guessing Foo3 should probably succeed, but the nested inductive compiler is a bit conservative there for simplicity.
bblaxill (Jun 10 2023 at 03:24):
It's unfortunate that
inductive Exp : Ty -> List Ty -> Type _ where
| app : forall x, (Exp (a ⊸ b) ★ Exp a) x -> Exp b ctx
is not accepted but the inlined version is. If I learn how to extend the elaborator would it be possible to enable this without inlining for my specific case? Or perhaps it is not possible because the check is in the kernel and there is no way to enable this without inlining. It's a syntactic issue of course but I'd very much prefer to avoid extra (un)packing even if it requires me to do additional work with e.g. the elaborator. Thanks!
Mario Carneiro (Jun 10 2023 at 03:29):
it is the latter, any extensions here have to be performed in the kernel and complicate any metatheory or soundness proofs for the system
Mario Carneiro (Jun 10 2023 at 03:31):
also, have you thought about what the Exp.rec
function would even look like?
bblaxill (Jun 10 2023 at 11:34):
I hadn't thought about it, but yes I can see there is an issue there. So is it correct to say Agda accepts these definitions because Agda pattern matching is a primitive of the language and doesnt elaborate pattern matching based on induction principles? I have come to theorem proving via non-academic route and am still trying to understand various fundamental concepts. thanks
Mario Carneiro (Jun 10 2023 at 13:30):
I'm not sure I would say that is an intentional consequence of the design decision, but it is certainly easier to accidentally allow unsound things when you have no conservative core language and just check everything directly in the frontend
Last updated: Dec 20 2023 at 11:08 UTC