Zulip Chat Archive
Stream: new members
Topic: Recursive membership instance
aron (May 05 2025 at 21:38):
Is it possible to define an instance of Membership recursively? I can't figure out how to do this for the below code. I can't define it directly in the Membership.mem instance because it's recursive, but I also can't do it in a separate function first because I want to rely on the Membership instance of List. How can I implement my instance correctly?
I also can't seem to automatically derive a DecidableEq instance for ZonkedSimpleType for some reason.
inductive Primitive
| int
| string
inductive TypeVar
| typeVar
mutual
inductive ZonkedSimpleType where
| primitive : Primitive → ZonkedSimpleType
| typeCtor : String → List ZonkedSimpleType → ZonkedSimpleType
| typeVar : TypeVar → ZonkedSimpleType
deriving DecidableEq
structure ZonkedPolyType where
typeVars : List TypeVar
type : ZonkedSimpleType
deriving DecidableEq
inductive ZonkedPolyOrMonoType where
| poly : ZonkedPolyType → ZonkedPolyOrMonoType
| mono : ZonkedSimpleType → ZonkedPolyOrMonoType
deriving DecidableEq
end
def ZonkedPolyOrMonoType.mem (container : ZonkedPolyOrMonoType) (simpleType : ZonkedSimpleType) : Prop :=
match container with
| .mono m =>
if m = simpleType then
True
else
match m with
| .primitive _ => False
| .typeCtor _ inners => List.instMembership.mem inners simpleType
| .typeVar _ => False
| .poly p => ZonkedPolyOrMonoType.mem p.type simpleType
instance instMembershipZonkedSimpleTypeZonkedPolyOrMonoType : Membership ZonkedSimpleType ZonkedPolyOrMonoType where
mem container simpleType :=
match container with
| .mono m =>
match m with
| .primitive _ => False
| .typeCtor _ inners => List.instMembership.mem inners simpleType
| .typeVar _ => False
| .poly p => instMembershipZonkedSimpleTypeZonkedPolyOrMonoType.mem p.type simpleType
Matt Diamond (May 05 2025 at 22:11):
there's an error in the poly case of ZonkedPolyOrMonoType.mem:
ZonkedPolyOrMonoType.mem p.type simpleType
the first argument to ZonkedPolyOrMonoType.mem has to be a ZonkedPolyOrMonoType, but p.type is a ZonkedSimpleType
I think there's a similar issue in instMembershipZonkedSimpleTypeZonkedPolyOrMonoType
Yan Yablonovskiy 🇺🇦 (May 06 2025 at 00:21):
aron said:
Is it possible to define an instance of
Membershiprecursively? I can't figure out how to do this for the below code. I can't define it directly in theMembership.meminstance because it's recursive, but I also can't do it in a separate function first because I want to rely on theMembershipinstance ofList. How can I implement my instance correctly?I also can't seem to automatically derive a
DecidableEqinstance forZonkedSimpleTypefor some reason.inductive Primitive | int | string inductive TypeVar | typeVar mutual inductive ZonkedSimpleType where | primitive : Primitive → ZonkedSimpleType | typeCtor : String → List ZonkedSimpleType → ZonkedSimpleType | typeVar : TypeVar → ZonkedSimpleType deriving DecidableEq structure ZonkedPolyType where typeVars : List TypeVar type : ZonkedSimpleType deriving DecidableEq inductive ZonkedPolyOrMonoType where | poly : ZonkedPolyType → ZonkedPolyOrMonoType | mono : ZonkedSimpleType → ZonkedPolyOrMonoType deriving DecidableEq end def ZonkedPolyOrMonoType.mem (container : ZonkedPolyOrMonoType) (simpleType : ZonkedSimpleType) : Prop := match container with | .mono m => if m = simpleType then True else match m with | .primitive _ => False | .typeCtor _ inners => List.instMembership.mem inners simpleType | .typeVar _ => False | .poly p => ZonkedPolyOrMonoType.mem p.type simpleType instance instMembershipZonkedSimpleTypeZonkedPolyOrMonoType : Membership ZonkedSimpleType ZonkedPolyOrMonoType where mem container simpleType := match container with | .mono m => match m with | .primitive _ => False | .typeCtor _ inners => List.instMembership.mem inners simpleType | .typeVar _ => False | .poly p => instMembershipZonkedSimpleTypeZonkedPolyOrMonoType.mem p.type simpleType
Recursive instances have some quirks/workarounds , have a look at this for possible solutions:
https://lean-lang.org/doc/reference/latest///Type-Classes/Instance-Declarations/#recursive-instances
aron (May 06 2025 at 11:11):
@Matt Diamond yeah oops you're right. I've modified my code a bit and now it seems like the main issue is that I can't automatically derive an instance of BEq or DecidableEq on my types, even for Primitive and TypeVar which should be trivially derivable imo?
inductive Primitive
| int
| string
deriving LawfulBEq
inductive TypeVar
| typeVar
deriving LawfulBEq
mutual
inductive ZonkedSimpleType where
| primitive : Primitive → ZonkedSimpleType
| typeCtor : String → List ZonkedSimpleType → ZonkedSimpleType
| typeVar : TypeVar → ZonkedSimpleType
deriving LawfulBEq
structure ZonkedPolyType where
typeVars : List TypeVar
type : ZonkedSimpleType
deriving LawfulBEq
inductive ZonkedPolyOrMonoType where
| poly : ZonkedPolyType → ZonkedPolyOrMonoType
| mono : ZonkedSimpleType → ZonkedPolyOrMonoType
deriving LawfulBEq
end
def ZonkedSimpleType.mem (container : ZonkedSimpleType) (simpleType : ZonkedSimpleType) : Prop :=
if h : container = simpleType then -- failed to synthesize `Decidable (container = simpleType)`
True
else
match container with
| .primitive _ => False
| .typeCtor _ inners => List.instMembership.mem inners simpleType
| .typeVar _ => False
def ZonkedPolyOrMonoType.mem (container : ZonkedPolyOrMonoType) (simpleType : ZonkedSimpleType) : Prop :=
match container with
| .mono m => ZonkedSimpleType.mem m simpleType
| .poly p =>
-- @TODO: need to instantiate the `typeVars` here before doing a straight comparison – because any type vars inside `simpleType` will be from a higher scope so will not be equal to the type vars in `p.typeVars`
ZonkedSimpleType.mem p.type simpleType
instance instMembershipZonkedSimpleTypeZonkedPolyOrMonoType : Membership ZonkedSimpleType ZonkedPolyOrMonoType where
mem container simpleType := ZonkedPolyOrMonoType.mem container simpleType
Aaron Liu (May 06 2025 at 11:16):
That's because you're trying to deriving LawfulBEq, deriving DecidableEq does work. (for Primitive and TypeVar at least)
aron (May 06 2025 at 11:33):
@Aaron Liu yeah I never know which of these to use. deriving DecidableEq does indeed work for most of them but not for ZonkedSimpleType for some reason. Do you know why not?
inductive Primitive
| int
| string
deriving DecidableEq
inductive TypeVar
| typeVar
deriving DecidableEq
mutual
inductive ZonkedSimpleType where
| primitive : Primitive → ZonkedSimpleType
| typeCtor : String → List ZonkedSimpleType → ZonkedSimpleType
| typeVar : TypeVar → ZonkedSimpleType
deriving DecidableEq -- default handlers have not been implemented yet, class: 'DecidableEq' types: [ZonkedSimpleType, ZonkedPolyType, ZonkedPolyOrMonoType]
structure ZonkedPolyType where
typeVars : List TypeVar
type : ZonkedSimpleType
deriving DecidableEq
inductive ZonkedPolyOrMonoType where
| poly : ZonkedPolyType → ZonkedPolyOrMonoType
| mono : ZonkedSimpleType → ZonkedPolyOrMonoType
deriving DecidableEq
end
def ZonkedSimpleType.mem (container : ZonkedSimpleType) (simpleType : ZonkedSimpleType) : Prop :=
if h : container = simpleType then -- failed to synthesize `Decidable (container = simpleType)`
True
else
match container with
| .primitive _ => False
| .typeCtor _ inners => List.instMembership.mem inners simpleType
| .typeVar _ => False
def ZonkedPolyOrMonoType.mem (container : ZonkedPolyOrMonoType) (simpleType : ZonkedSimpleType) : Prop :=
match container with
| .mono m => ZonkedSimpleType.mem m simpleType
| .poly p =>
-- @TODO: need to instantiate the `typeVars` here before doing a straight comparison – because any type vars inside `simpleType` will be from a higher scope so will not be equal to the type vars in `p.typeVars`
ZonkedSimpleType.mem p.type simpleType
instance instMembershipZonkedSimpleTypeZonkedPolyOrMonoType : Membership ZonkedSimpleType ZonkedPolyOrMonoType where
mem container simpleType := ZonkedPolyOrMonoType.mem container simpleType
Aaron Liu (May 06 2025 at 11:36):
It's because they're mutual types and those are more complicated, no one has written in support for deriving DecidableEq on those mutual inductives yet.
Aaron Liu (May 06 2025 at 11:37):
It looks like you don't actually use them recursively though, so why are they mutual?
aron (May 06 2025 at 13:22):
by "mutual types" do you mean that they're in a mutual block? because you're right I'm not using them mutually atm (I did in an earlier version of the code but I don't think they need to be anymore) but even when I remove the mutual block deriving DecidableEq still doesn't work :/
inductive Primitive
| int
| string
deriving DecidableEq
inductive TypeVar
| typeVar
deriving DecidableEq
inductive ZonkedSimpleType where
| primitive : Primitive → ZonkedSimpleType
| typeCtor : String → List ZonkedSimpleType → ZonkedSimpleType
| typeVar : TypeVar → ZonkedSimpleType
deriving DecidableEq -- default handlers have not been implemented yet, class: 'DecidableEq' types: [ZonkedSimpleType]
aron (May 06 2025 at 13:27):
unless you mean the issue is that ZonkedSimpleType contains recursive ZonkedSimpleTypes? but why is that an issue?
Aaron Liu (May 06 2025 at 13:29):
I mean that they have mutual recursors, which is partially the mutual block's fault
Aaron Liu (May 06 2025 at 13:30):
If you #print ZonkedSimpleType.rec you see multiple motives, because you use it recursively inside the List.
aron (May 06 2025 at 13:32):
If you
#print ZonkedSimpleType.recyou see multiple motives, because you use it recursively inside theList.
hm ok I don't 100% understand what that means, but either way, ok. Why is that a problem? Why is a ctor param being inside a recursive type an issue?
aron (May 06 2025 at 13:33):
and is there a way for me to solve this? what "default handler" is lean looking for and how can I provide it?
Aaron Liu (May 06 2025 at 13:35):
aron said:
Why is that a problem? Why is a ctor param being inside a recursive type an issue?
Because those are more complicated, and someone has to implement the DecidableEq handler, and no one has done it for recursive types yet.
Aaron Liu (May 06 2025 at 13:36):
aron said:
and is there a way for me to solve this? what "default handler" is lean looking for and how can I provide it?
You can just implement the DecidableEq yourself, without relying on a handler.
aron (May 07 2025 at 08:19):
Ok here's where I'm up to so far trying to implement DecidableEq myself. For some reason Lean can't figure out that the my instance is structurally recursive so I need to prove termination myself.
inductive Primitive
| int
| string
deriving DecidableEq
inductive TypeVar
| typeVar
deriving DecidableEq
inductive ZonkedSimpleType where
| primitive : Primitive → ZonkedSimpleType
| typeCtor : String → List ZonkedSimpleType → ZonkedSimpleType
| typeVar : TypeVar → ZonkedSimpleType
def ZonkedSimpleType.typeVar_inj a b : ZonkedSimpleType.typeVar a = .typeVar b → a = b := by
intro h
simp
instance instDecidableEqZonkedSimpleType : DecidableEq ZonkedSimpleType :=
fun a b =>
match ha : a, hb : b with
| .primitive p1, .primitive p2 => by
match decEq p1 p2 with
| isTrue h =>
simp
exact isTrue h
| isFalse h =>
simp
exact isFalse h
| .typeVar tv1, .typeVar tv2 => by
match hh : decEq tv1 tv2 with
| isTrue h =>
refine isTrue ?_
simp
| isFalse h =>
refine isFalse ?_
intro hhh
have := ZonkedSimpleType.typeVar_inj tv1 tv2 hhh
rw [this] at h
contradiction
| .typeCtor name1 inners1, .typeCtor name2 inners2 => by
let inners := @List.hasDecEq ZonkedSimpleType instDecidableEqZonkedSimpleType inners1 inners2
match decEq name1 name2, inners with
| isTrue h1, isTrue h2 =>
simp
exact isTrue ⟨h1, h2⟩
| isFalse h1, isFalse h2 =>
simp
refine isFalse ?_
intro h
rw [h.left] at h1
contradiction
| isFalse h1, isTrue h2 =>
simp
refine isFalse ?_
intro h
rw [h.left] at h1
contradiction
| isTrue h1, isFalse h2 =>
simp
refine isFalse ?_
intro h
rw [h.right] at h2
contradiction
| .primitive _, .typeCtor _ _
| .primitive _, .typeVar _
| .typeCtor _ _, .primitive _
| .typeCtor _ _, .typeVar _
| .typeVar _, .primitive _
| .typeVar _, .typeCtor _ _ => by
all_goals
refine isFalse ?_
intro h
simp at h
termination_by a b => a
decreasing_by
simp
But my proof state is:
unsolved goals
a✝ b✝ : ZonkedSimpleType
name1 : String
inners1 : List ZonkedSimpleType
name2 : String
inners2 : List ZonkedSimpleType
ha : a✝ = ZonkedSimpleType.typeCtor name1 inners1
hb : b✝ = ZonkedSimpleType.typeCtor name2 inners2
a b : ZonkedSimpleType
⊢ sizeOf a < 1 + sizeOf name1 + sizeOf inners1
The problem is I don't know which a this is referring to, because there are no other hypotheses about a available. So there's nothing else I can say about a. How do I solve this?
Robin Arnez (May 07 2025 at 11:05):
I don't think this construction is possible (using List.hasDecEq). This probably works with mutual structural recursion though, one for DecidableEq ZonkedSimpleType and another for DecidableEq (List ZonkedSimpleType)
Robin Arnez (May 07 2025 at 11:16):
Here:
inductive Primitive
| int
| string
deriving DecidableEq
inductive TypeVar
| typeVar
deriving DecidableEq
inductive ZonkedSimpleType where
| primitive : Primitive → ZonkedSimpleType
| typeCtor : String → List ZonkedSimpleType → ZonkedSimpleType
| typeVar : TypeVar → ZonkedSimpleType
mutual
def decEqListZonkedSimpleType : DecidableEq (List ZonkedSimpleType) := fun a b =>
match a, b with
| [], [] => .isTrue rfl
| _ :: _, [] => .isFalse List.noConfusion
| [], _ :: _ => .isFalse List.noConfusion
| x :: l, y :: l' =>
match instDecidableEqZonkedSimpleType x y with
| .isFalse h => .isFalse (fun h' => List.noConfusion h' fun h'' _ => h h'')
| .isTrue h =>
match decEqListZonkedSimpleType l l' with
| .isFalse h2 => .isFalse (fun h' => List.noConfusion h' fun _ h'' => h2 h'')
| .isTrue h2 => .isTrue (h ▸ h2 ▸ rfl)
termination_by structural a => a
instance : DecidableEq ZonkedSimpleType := fun a b =>
match a, b with
| .primitive x, .primitive y =>
match instDecidableEqPrimitive x y with
| .isFalse h => .isFalse (fun h' => ZonkedSimpleType.noConfusion h' h)
| .isTrue h => .isTrue (h ▸ rfl)
| .typeVar x, .typeVar y =>
match instDecidableEqTypeVar x y with
| .isFalse h => .isFalse (fun h' => ZonkedSimpleType.noConfusion h' h)
| .isTrue h => .isTrue (h ▸ rfl)
| .typeCtor s l, .typeCtor s' l' =>
match instDecidableEqString s s' with
| .isFalse h => .isFalse (fun h' => ZonkedSimpleType.noConfusion h' fun h'' _ => h h'')
| .isTrue h =>
match decEqListZonkedSimpleType l l' with
| .isFalse h2 => .isFalse (fun h' => ZonkedSimpleType.noConfusion h' fun _ h'' => h2 h'')
| .isTrue h2 => .isTrue (h ▸ h2 ▸ rfl)
| .primitive .., .typeCtor .. => .isFalse ZonkedSimpleType.noConfusion
| .primitive .., .typeVar .. => .isFalse ZonkedSimpleType.noConfusion
| .typeCtor .., .primitive .. => .isFalse ZonkedSimpleType.noConfusion
| .typeCtor .., .typeVar .. => .isFalse ZonkedSimpleType.noConfusion
| .typeVar .., .primitive .. => .isFalse ZonkedSimpleType.noConfusion
| .typeVar .., .typeCtor .. => .isFalse ZonkedSimpleType.noConfusion
end
Aaron Liu (May 07 2025 at 11:17):
This will cause diamonds, you should probably make the instance : DecidableEq (List ZonkedSimpleType) a def.
Robin Arnez (May 07 2025 at 11:19):
Oh yeah, you're right, I edited my message
aron (May 07 2025 at 12:49):
@Robin Arnez ooh ok this seems to work!
What is .noConfusion? it seems to be an automatically generated method for every inductive? what does it do, what is it for?
And how does this approach let us avoid proving termination? (it looks like the termination_by structural a => a isn't even needed btw)
aron (May 07 2025 at 12:49):
plus a few more questions:
- it looks like
instDecidableEqZonkedSimpleTypeis an automatically generated name for the instance? same forinstDecidableEqPrimitiveandinstDecidableEqTypeVar? is this true for all typeclass instances? how do I know what name is generated for each instance? - is
.typeVar ..syntax sugar for.typeVar _ _? does this always work? is it just one.per_? is the benefit of this syntax that you don't need to put spaces between the dots?
Aaron Liu (May 07 2025 at 12:55):
.. is sugar for "add as many underscores as possible, based on the type". The benefit of this is you don't need to remember how many underscores to add.
Robin Arnez (May 07 2025 at 13:01):
noConfusion is an automatically generated declaration for every inductive type. It is used to power contradiction and simp only [reduceCtorEq] and all Induct.ctor.inj and Induct.ctor.injEq lemmas. It takes in an equality a = b and produces noConfusionType P a b. noConfusionType is just P if a and b are different constructors (i.e. it gives you a value of any type you want) and otherwise it's a function of the form (a = b -> c = d -> ... -> P) -> P (i.e. if you get P for a pairwise equality of all fields, then you get P).
The problem with using List.decEq is that lean can't know (or doesn't try to infer) how instDecidableEqZonkedSimpleType will be called, for all it knows List.decEq could call it on any two values, causing it to go in an infinite loop. When writing out List.decEq ourselves in mutual recursion it can take into account how it does recursion.
The names for instances are generated automatically and are usually of the shape inst + everything occurring in the type from left to right.
Last updated: Dec 20 2025 at 21:32 UTC