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 use RBNode, 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 Foomust 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 and Bar 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.

image.png

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 the Below if Prod 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 thenin mkBelow with if x.isRec && !x.isNested thenfixes 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