-
Notifications
You must be signed in to change notification settings - Fork 421
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: manage nested inductive types in deriving
#3160
Conversation
WIP |
f5cc115
to
b09faaa
Compare
leanprover-community-mathlib4-bot
commented
Jul 27, 2024
•
edited by leanprover-community-bot
Loading
edited by leanprover-community-bot
Mathlib CI status (docs):
|
cbc90af
to
4d37d6e
Compare
4004db4
to
901d3cd
Compare
901d3cd
to
3e4b79f
Compare
Oh, did I miss that this is ready for review? The label still says WIP. |
Whoops, forgot to change that. This is indeed ready for review, I sent Kim some details in DM regarding that PR, but I might as well put it all in here for documentation purposes:
inductive Foo (α : Type u) : Type u :=
| foo : List (Option (Foo α)) → Foo α
deriving DecidableEq Will generate a mutual block with the definitions: mutual
def decEqOptionFoo {α} [DecidableEq α] (x : Option (Foo α)) (x1 : Option (Foo α)) : Decidable (x = x1) := ...
def decEqListOptionFoo {α} [DecidableEq α] (x : List (Option (Foo α))) (x1 : List (Option (Foo α))) : Decidable (x = x1) := ...
def decEqFoo {α} [DecidableEq α] (x : Foo α) (x1 : Foo α) : Decidable (x = x1) := ...
end
When first working on the PR, I was hoping to be able to use these modifications to The PR currently passes CI/CL on Lean 4, but fails to compile batteries and mathlib.
|
awaiting-review |
Thanks! Maybe worth doing the mathlib adaption on a branch to be sure that it works before we merge? Would it be too much work to put the JSON refactoring into a PR of it's own, or would that require work that is then thrown away again? |
I think putting the JSON refactoring in a separate PR is doable and would be beneficial, I'll start working on that. I'll be making a mathlib PR fixing their derive handler as well. Until this two things are done, and the former has been merged, I'm putting this PR back to |
WIP |
Thanks! Let me know if you need anything from me |
I think I was wondering whether and why it needs a stage0 update. |
Hmm, I remember having some build failures on |
29d2785
to
5e01b49
Compare
Playing around with it a bit I notice that for
we get
but
Where in the code is the decision made to translate them differently? I’m a bit worried about the latter. While all lean4/src/Init/Data/Array/DecidableEq.lean Lines 47 to 51 in e9e858a
The author of the But I also don’t see an easy alternative. Maybe after creating Which other instances are not using |
I'm not sure I would call the |
This is dictated by the
Right. This is the biggest issue with the current PR IMO, and I plann on tackling this in a later PR if that's acceptable (I have not yet started working on any proof-of-concept for such a PR).
This is my initial solution to the problem, but it would impose some challenges (for example, if some instance is defined using some
For now, only
I agree that it would be ideal to get rid of |
What I mean is that for every Type t, the Type |
I don't think it would be an issue on |
Would be nice indeed, but I don't really have a good idea. Maybe with #4749 and with all functions involved refined using primitive recursion? But that won't help when a user wrote an off standard method. For the present PR I am hesitant to merge it while we get the unexpected behavior. Maybe the Or, even less ambitious: somehow keep track of which instances were derived and only derive nested instances where all involved type formers have instances that were also derived and where we thus know that they behave like the one we are writing. Sorry for not thinking of this issue before! |
I think the
For example, the code generated for the array tree would look like this: inductive Tree where | node : Array Tree → Tree
noncomputable local instance : Inhabited (DecidableEq α) := ⟨Classical.typeDecidableEq _⟩ in
partial def decEqTreePartial (x4 : @Tree) (x5 : @Tree) : Decidable (x4 = x5) :=
let inst : DecidableEq Tree := decEqTreePartial
match x4, x5 with
| @Tree.node a3, @Tree.node b3 =>
if h4 : @a3 = @b3 then by subst h4; exact isTrue rfl
else isFalse (by intro n3; injection n3; contradiction)
mutual
def decEqListTree (x : List Tree) (x1 : List Tree) : Decidable (x = x1) :=
match x, x1 with
| @List.nil _, @List.nil _ => isTrue rfl
| List.nil .., List.cons .. => isFalse (by intro h; injection h)
| List.cons .., List.nil .. => isFalse (by intro h; injection h)
| @List.cons _ a a1, @List.cons _ b b1 =>
let inst := decEqTree a b;
if h1 : a = b then by subst h1; exact (
let inst1 := decEqListTree @a1 @b1
if h2 : a1 = b1 then
by subst h2; exact isTrue rfl
else
isFalse (by intro n; injection n; contradiction)
)
else isFalse (by intro n1; injection n1; contradiction)
termination_by structural x
def decEqArrayTree (x2 : @Array (@Tree)) (x3 : @Array (@Tree)) : Decidable (x2 = x3) :=
match x2, x3 with
| @Array.mk _ a2, @Array.mk _ b2 =>
let inst2 := decEqListTree @a2 @b2;
if h3 : @a2 = @b2 then by subst h3; exact isTrue rfl
else isFalse (by intro n2; injection n2; contradiction)
termination_by structural x2
@[implemented_by decEqTreePartial]
def decEqTree (x4 : Tree) (x5 : Tree) : Decidable (x4 = x5) :=
match x4, x5 with
| Tree.node a3, Tree.node b3 =>
let inst3 := decEqArrayTree a3 b3;
if h4 : a3 = b3 then by subst h4; exact isTrue rfl
else isFalse (by intro n3; injection n3; contradiction)
termination_by structural x4
end
instance : DecidableEq Tree :=
decEqTree |
Sorry, but I'm not getting it. What is the purpose of |
|
Ah, now I see it, on the laptop the code is easier to understand :-) And the purpose of
is merely to to avoid
right? Would it be better or worse to omit that and use Hmm, not bad overall. Better or worse than this variant? inductive Tree where | node : Array Tree → Tree
mutual
def decEqListTree (x : List Tree) (x1 : List Tree) : Decidable (x = x1) :=
match x, x1 with
| @List.nil _, @List.nil _ => isTrue rfl
| List.nil .., List.cons .. => isFalse (by intro h; injection h)
| List.cons .., List.nil .. => isFalse (by intro h; injection h)
| @List.cons _ a a1, @List.cons _ b b1 =>
let inst := decEqTree a b;
if h1 : a = b then by subst h1; exact (
let inst1 := decEqListTree @a1 @b1
if h2 : a1 = b1 then
by subst h2; exact isTrue rfl
else
isFalse (by intro n; injection n; contradiction)
)
else isFalse (by intro n1; injection n1; contradiction)
termination_by structural x
def decEqArrayTree (x2 : @Array (@Tree)) (x3 : @Array (@Tree)) : Decidable (x2 = x3) :=
match x2, x3 with
| @Array.mk _ a2, @Array.mk _ b2 =>
let inst2 := decEqListTree @a2 @b2;
if h3 : @a2 = @b2 then by subst h3; exact isTrue rfl
else isFalse (by intro n2; injection n2; contradiction)
termination_by structural x2
def decEqTree (x4 : Tree) (x5 : Tree) : Decidable (x4 = x5) :=
match x4, x5 with
| Tree.node a3, Tree.node b3 =>
let inst3 := decEqArrayTree a3 b3;
if h4 : a3 = b3 then by subst h4; exact isTrue rfl
else isFalse (by intro n3; injection n3; contradiction)
termination_by structural x4
end
instance : DecidableEq Tree :=
decEqTree
-- now patch up the implementations of any nested derived instance
def realDecEqArrayTree (x2 : @Array (@Tree)) (x3 : @Array (@Tree)) : Decidable (x2 = x3) := inferInstance
@[csimp]
theorem decEqArrayTree_eq : @decEqArrayTree = @realDecEqArrayTree := by
ext t1 t2; apply Subsingleton.allEq It's still not perfect in the cases where the nested type has a manually written (maybe more efficient) |
The local instance is just used to avoid the error message, and using In both cases, having a non-ideal kernel reduction seems unavoidable sadly. The ultimate solution would still be to use the already existing instance in the mutual block, but that would open another can of worms I don't have the time to get into currently, hence why I'd like to work on that in a follow-up PR. If we assume that this is a temporary hack, I would tend towards using the |
Either hack is fine with me, if we want a hack at all. I'm not sure if the feature “derivable decidable equality for nested induction” itself is worth introducing hacks and complications for - how pressing is it for our users? I'll also check back with Leo, as the reporter of #2329. |
Refactors the derive handlers for `ToJson` and `FromJson` in preparation for leanprover#3160. This splits up the different parts of the handler according to how other similar handlers are implemented while keeping the original logic intact. This makes the changes necessary to adapt the file in leanprover#3160 much easier.
Ah, forgot to report back. I don’t think we had a good solution either. The safest bet would be to fail instead of deriving possibly bad nested inductives, with a good error message that points to a github issue where users can complain. The next safest bet might be an internal attribute that we use to track “this type’s |
I can certainly add a new error message saying explicitly that nested inductive types are not currently supported to replace the
I still believe "specializing" an already existing instance inside our mutual block is the right way to go in the long run, despite the technical challenges it would impose. In any case, I do not have time to allocate to this anytime soon. Adding to that that the PR needs a rework to get rid of |
It pains me if that’s the conclusion, given the work that went into this PR. Sometimes that’s just how it goes, but still unfortunate. The refactoring to use But the other, semantic problem remains, and we might have to park this until we have a better plan. (Just to double-check: For the instances that use |
I don't hold a negative sentiment WRT closing the PR, better have no code merged than some bad one. It's a learning experience either way, I should not have dived into my design choices before first discussing it with someone, doing so would have helped with seeing these issues earlier.
There shouldn't be any change in the behaviour of handlers that use |
Adds support for handling nested inductive types in derive handlers
This changes the
Deriving
codebase a fair bit, I'll probably need to split this into multiple PRs.In particular, the file
FromToJson.lean
has been refactored a fair bit.(Will add a more thorough description once the PR is stable)
Closes #2329