Zulip Chat Archive
Stream: lean4
Topic: more flexible nested inductives
Arthur Adjedj (Aug 07 2023 at 11:43):
Mario Carneiro said:
While you can't use
RBMap
in a nested inductive, you can useRBNode
, the inductive it is defined from
I still hold the belief that Lean would benefit from having either a somehow smarter way to detect strict positivity, or a way for the end-user to annotate things as strictly positive. HashMap
and RBMap
would benefit a lot from that imo
Mario Carneiro (Aug 07 2023 at 11:49):
Sigma types are fundamentally not strictly positive though
Mario Carneiro (Aug 07 2023 at 11:50):
They are a pair of an element of T
and an element of an element of T -> Prop
Mario Carneiro (Aug 07 2023 at 11:50):
And an annotation is hard because this is a kernel-level check
Arthur Adjedj (Aug 07 2023 at 11:51):
While it's obviously not strictly positive over the first argument, would Sigma A
not be strictly positive for any A ?
Mario Carneiro (Aug 07 2023 at 11:52):
What is Sigma A
? Sigma
takes two arguments
Arthur Adjedj (Aug 07 2023 at 11:53):
the type Sigma A
, given any type A
Mario Carneiro (Aug 07 2023 at 11:53):
inductive Sigma.{u, v} : {α : Type u} → (α → Type v) → Type (max u v)
Mario Carneiro (Aug 07 2023 at 11:53):
sorry, the first one is implicit. But I still don't know what you mean
Mario Carneiro (Aug 07 2023 at 11:53):
because the explicit argument of Sigma
is not a type
Arthur Adjedj (Aug 07 2023 at 11:54):
oops, you're right, nevermind then
Arthur Adjedj (Aug 07 2023 at 11:55):
Mario Carneiro said:
And an annotation is hard because this is a kernel-level check
Other proof-assistants (namely, Coq) do allow for the modification of certain kernel-checks through the use of pragmas/flags though
Mario Carneiro (Aug 07 2023 at 11:55):
in some cases, with heavy reworking, it is possible to rephrase sigma types as inductive types by pushing properties into the leaves. This creates very dependent types though so I am wary of this approach
Mario Carneiro (Aug 07 2023 at 11:56):
Arthur Adjedj said:
Mario Carneiro said:
And an annotation is hard because this is a kernel-level check
Other proof-assistants (namely, Coq) do allow for the modification of certain kernel-checks through the use of pragmas/flags though
yep, this is never going to happen, we want a better soundness story than that
Arthur Adjedj (Aug 07 2023 at 11:56):
Alright, makes sense.
Mario Carneiro (Aug 07 2023 at 11:56):
also Coq would not allow this type, for the same reason
Arthur Adjedj (Aug 07 2023 at 11:57):
It would, if you tagged the inductive definition of BkTree
with a tag to cancel the positivity-checking on it.
Arthur Adjedj (Aug 07 2023 at 12:00):
Some much more innocuous inductive types could also stand to be accepted by the positivity-checker. Agda is smarter for that matter. For example, the following gets accepted by Agda, but not by lean:
structure Foo (x : Type → Type) where
inductive Bar : Type → Type :=
| bar : Bar (Foo Bar)
Mario Carneiro (Aug 07 2023 at 12:09):
I'm not sure I would describe that type as innocuous
Mario Carneiro (Aug 07 2023 at 12:12):
specifically, what are the bounds on when such types are accepted, and where is the paper proof of soundness of such an extension? I wouldn't even want to consider an extension without that
Arthur Adjedj (Aug 07 2023 at 12:15):
I would have to dive into Agda's codebase again, but I believe the criteria is that Foo
must be strictly positivive. As for a soundness proof of that extension, I do not have a paper-proof justification in hand, and would need to look into that. Such extension is however useful for formalising things like logical relations of other type theories, and is an issue I encountered when trying to port a formalisation from Agda to Lean.
Arthur Adjedj (Aug 07 2023 at 12:16):
I do however have an example in hand of a justifyably sound inductive type that doesn't get accepted by lean, let me find it again.
Mario Carneiro (Aug 07 2023 at 12:16):
My opinion of the agda soundness story is very low, so "agda can do it, why can't lean?" isn't very persuasive to me
Arthur Adjedj (Aug 07 2023 at 12:19):
here
I don't know if lean 4 still compiles nested inductives to mutual ones, but it seems to have trouble compiling down nested inductive predicates. I should be able to make a MWE quickly enough. This sort of stuff is accepted by Coq with no issue.
Mario Carneiro (Aug 07 2023 at 12:19):
I think the example you gave above is also justifiably sound (take Foo x := Unit
and Bar x := if x = Unit then Unit else Empty
), but that's just one example, not a whole scheme
Mario Carneiro (Aug 07 2023 at 12:20):
Lean 4 does not compile nested inductives to mutual
Mario Carneiro (Aug 07 2023 at 12:20):
but it does a check that should be equivalent to the existence of such a reduction
Mario Carneiro (Aug 07 2023 at 12:22):
and it needs to compute essentially the reduction in order to determine the structure of the recursor
Mario Carneiro (Aug 07 2023 at 12:22):
Does agda even have recursors for these types?
Arthur Adjedj (Aug 07 2023 at 12:24):
Mario Carneiro said:
I think the example you gave above is also justifiably sound (take
Foo x := Unit
andBar x := if x = Unit then Unit else Empty
), but that's just one example, not a whole scheme
It is ! In general, there is a way to re-encode such types by "inlining" the structure Foo
in some way, at least as long as Foo is not recursive. I've had to do that on a more complex example myself, and there hasn't been any fundamental issue to doing the translation.
Arthur Adjedj (Aug 07 2023 at 12:24):
Mario Carneiro said:
Does agda even have recursors for these types?
Agda doesn't generate recursors for anything
Arthur Adjedj (Aug 07 2023 at 12:26):
Arthur Adjedj said:
here
I don't know if lean 4 still compiles nested inductives to mutual ones, but it seems to have trouble compiling down nested inductive predicates. I should be able to make a MWE quickly enough. This sort of stuff is accepted by Coq with no issue.
Here is an MWE:
inductive Forall {α : Type u} (p : α → Prop) : α → Prop
inductive Foo {α : Type u} : α → Prop :=
| foo : (a : α) → Forall Foo a → Foo a
/-only trivial inductive applications supported in premises:
Forall Foo a-/
Mario Carneiro (Aug 07 2023 at 12:27):
and what would the recursor be?
Mario Carneiro (Aug 07 2023 at 12:28):
in that example, lean won't even allow it with unsafe inductive
, probably because it has no idea how to generate the recursor
Arthur Adjedj (Aug 07 2023 at 12:31):
Coq manages to generate one:
Inductive Forall {α : Type} (p : α -> Prop) : α -> Prop :=.
Inductive Foo {α : Type} : α -> Prop :=
| foo (a : α) : Forall Foo a -> Foo a.
Check Foo_ind. (*Foo_ind
: forall (α : Type) (P : α -> Prop), (forall a : α, Forall Foo a -> P a) -> forall α0 : α, Foo α0 -> P α0*)
Mario Carneiro (Aug 07 2023 at 12:32):
what does that look like with | mk : p a -> Forall p a
?
Mario Carneiro (Aug 07 2023 at 12:32):
and also | mk : (p a -> False) -> Forall p a
(I hope this is rejected)
Mario Carneiro (Aug 07 2023 at 12:33):
That induction principle looks odd because it doesn't have two motives, even though there are two inductives involved
Arthur Adjedj (Aug 07 2023 at 12:33):
(jscoq gives horrible copy-pasted stuff, so I'm sending screenshots)
The former gives this:
image.png
The second doesn't get accepted.
Mario Carneiro (Aug 07 2023 at 12:34):
wait a minute, that's the same as the first one
Arthur Adjedj (Aug 07 2023 at 12:34):
even with p a
, it doesn't add a new motive
Mario Carneiro (Aug 07 2023 at 12:35):
I think this recursor is not (intended to be) complete, it's only good enough to do induction over the Foo
part without any mutual inductions that have to bounce back and forth between Foo
and Forall Foo
Arthur Adjedj (Aug 07 2023 at 12:35):
Coq's auto-generated recursors are broken on nested/mutual inductives, I'd have to do some Scheme Induction
stuff to retrieve the correct ones, and I don't remember how
Arthur Adjedj (Aug 07 2023 at 12:35):
I'll look into it
Mario Carneiro (Aug 07 2023 at 12:36):
in which case it may well be the case that no one has ever worked out the full scheme for nested inductives in coq and/or agda
Arthur Adjedj (Aug 07 2023 at 12:37):
Inductive Forall {α : Type} (p : α -> Prop) : α -> Prop :=
| mk (a : α): p a -> Forall p a.
Inductive Foo {α : Type} : α -> Prop :=
| foo (a : α) : Forall Foo a -> Foo a.
Scheme Foo_ind_2 := Induction for Foo Sort Prop.
Check Foo_ind_2.
Arthur Adjedj (Aug 07 2023 at 12:38):
It doesn't add a motive here, but does do some "inlining" trick by asking for the motive to be indexed by α
Mario Carneiro (Aug 07 2023 at 12:38):
that looks like it is treating alpha as an index rather than a parameter
Mario Carneiro (Aug 07 2023 at 12:39):
that's usually undesirable, it makes the scheme weaker
Mario Carneiro (Aug 07 2023 at 12:39):
oh wait nvm, alpha0 is an element of type alpha
Mario Carneiro (Aug 07 2023 at 12:40):
okay yeah, this is just casesOn
Mario Carneiro (Aug 07 2023 at 12:41):
I don't think there is any inlining happening here, you would get the same result if the argument to foo
was of some other type B
Arthur Adjedj (Aug 07 2023 at 12:42):
you're right
Arthur Adjedj (Aug 07 2023 at 12:44):
In any case, the same inductive definitions are allowed in Type
in lean
Arthur Adjedj (Aug 07 2023 at 12:44):
However, it wouldn't make sense for lean to have trouble knowing whether Foo
is a subsingleton here, since it's pretty obviously indexed
Arthur Adjedj (Aug 07 2023 at 12:47):
And also makes a seemingly correct recursors when defining this over Type
:
inductive Forall (p : α → Type) : α → Type
inductive Foo : α → Type :=
| foo : (a : α) → Forall Foo a → Foo a
#check @Foo.rec
/-{α : Sort u_2} →
{a : α} →
{motive_1 : Foo a → Sort u_1} →
{motive_2 : Forall Foo a → Sort u_1} →
((a_1 : Forall Foo a) → motive_2 a_1 → motive_1 (Foo.foo a a_1)) → (t : Foo a) → motive_1 t-/
Arthur Adjedj (Aug 07 2023 at 12:49):
(Also, this conversation has strayed far away from termination-checking, maybe it's would be good to move it in some way ?)
Notification Bot (Aug 07 2023 at 12:50):
57 messages were moved here from #new members > Termination checking for dummies by Mario Carneiro.
Mario Carneiro (Aug 07 2023 at 12:52):
I have to admit I have not yet carefully studied the rules for nested/mutual inductives in lean 4 like I did for lean 3 (which only had plain inductives and simulated the other stuff). It is not immediately obvious to me why the type being in Prop
should matter here, but the error message suggests that it is an incompleteness in the implementation
Arthur Adjedj (Aug 07 2023 at 12:52):
Should I open an issue then ?
Mario Carneiro (Aug 07 2023 at 12:53):
it would be best to have an implementation in hand first
Arthur Adjedj (Aug 07 2023 at 12:54):
I could find myself spending some time into trying to fix this
Mario Carneiro (Aug 07 2023 at 12:54):
I see now that the specific error message thrown here: only trivial inductive applications supported in premises
is caused not by the kernel but the elaborator, when trying to create the Foo.Below
type, which makes sense, these generally can't be created for Prop
inductives
Mario Carneiro (Aug 07 2023 at 12:54):
there is an entirely different strategy used for prop inductives
Mario Carneiro (Aug 07 2023 at 12:55):
and perhaps it is not as complete
Arthur Adjedj (Aug 07 2023 at 12:57):
Mario Carneiro said:
it would be best to have an implementation in hand first
Speaking of implementation, I've made a fix to the transitivity issue of defeq regarding eta-for unit some time ago (#2258), but was hesitant on making a PR for that, would it be worth doing one ?
Mario Carneiro (Aug 07 2023 at 12:57):
unfortunately there doesn't seem to be an option to disable auxiliary constructions, although you could call the kernel directly
Mario Carneiro (Aug 07 2023 at 12:59):
another hack is to define the type from an empty prelude
file (it won't generate the Below
if Prod
hasn't been defined yet), although lots of other things might break
Arthur Adjedj (Aug 07 2023 at 12:59):
Mario Carneiro said:
unfortunately there doesn't seem to be an option to disable auxiliary constructions, although you could call the kernel directly
I've tried in the past to do that, but didn't manage to write something working. I might try my hand at it again
Arthur Adjedj (Aug 07 2023 at 13:00):
Mario Carneiro said:
another hack is to define the type from an empty
prelude
file (it won't generate theBelow
ifProd
hasn't been defined yet), although lots of other things might break
prelude
doesn't help, I still get the same error here:
prelude
inductive Forall (p : α → Prop) : α → Prop
inductive Foo : α → Prop :=
| foo : (a : α) → Forall Foo a → Foo a
Mario Carneiro (Aug 07 2023 at 13:04):
oh interesting, apparently there are two implementations of mkBelow
, one in C++ and one in lean
Arthur Adjedj (Aug 07 2023 at 13:06):
yeah, it sems like all the helper functions like noConfusion
, CasesOn
etc.. have corresponding C++ versions
Mario Carneiro (Aug 07 2023 at 13:07):
the C++ mk_below
ignores inductive predicates, the lean one throws an error
Arthur Adjedj (Aug 07 2023 at 13:09):
I'm confused then, why would I still get that error on prelude ?
Mario Carneiro (Aug 07 2023 at 13:09):
elabInductiveViews
calls mkBelow
twice, once via mkInductiveDecl
-> mkAuxConstructions
(which checks for Prod
) and once directly (unconditional)
Mario Carneiro (Aug 07 2023 at 13:11):
actually the first one is calling Lean.mkBelow
, the C++ one
Mario Carneiro (Aug 07 2023 at 13:11):
which only does non-indpreds
Mario Carneiro (Aug 07 2023 at 13:11):
the second one is IndPredBelow.mkBelow
, which fails on this inductive
Arthur Adjedj (Aug 07 2023 at 18:18):
Looking more into this, i've just realised that despite the red scary error message, Foo
is still accepted, and all helpers other than below
(and noConfusion
, but Prop
types shouldn't have one so all good). Also, this is a new regression from lean 4, the same piece of code works with no issue in Lean 3
Mario Carneiro said:
there is an entirely different strategy used for prop inductives
I couldn't find any piece of code which suggested that Prop
-inductives are treated any differently when generating below
lemmas, do you know where the difference in approach happens ?
Mario Carneiro (Aug 07 2023 at 19:19):
@Arthur Adjedj the C++ function mk_below
starts with
if (is_inductive_predicate(env, n))
return env;
meaning that it does nothing on inductive predicates, and conversely IndPredBelow.mkBelow
is wrapped in
def mkBelow (declName : Name) : MetaM Unit := do
if (← isInductivePredicate declName) then
<stuff>
else trace[Meta.IndPredBelow] "Not inductive predicate"
meaning that it only does something on inductive predicates
Arthur Adjedj (Aug 07 2023 at 19:56):
As it turns out, any nested inductive predicate triggers the error, it just so happens that no one uses them:
inductive Foo : Prop → Prop
inductive Bar : Prop
| bar : Foo Bar → Bar
/-only trivial inductive applications supported in premises:
Foo Bar-/
Replacing the condition if x.isRec then
in mkBelow
with if x.isRec && !x.isNested then
fixes the issue :) Should I make an issue + PR ? @Mario Carneiro
Mario Carneiro (Aug 07 2023 at 19:58):
yeah, generating nothing sounds better than erroring
Arthur Adjedj (Aug 07 2023 at 19:59):
I don't think there is a general scheme to generate below
lemmas for inductive predicate since those may heavily depend on the shape of the nested application, simply not generating them might be better for now.
Last updated: Dec 20 2023 at 11:08 UTC