Zulip Chat Archive
Stream: new members
Topic: Decidable Equalities,
Jonathan Whitehead (May 14 2023 at 23:52):
I read through https://hrmacbeth.github.io/math2001/10_Relations.html to get a feel for relations in Lean. The problem that I'm having is that Lean can't seem to check equality between two structs unless I first build a framework for equality. In continuation of a previous post that I made, consider the simple example of a structure:
structure Vertex :=
(name : String)
I want to be able to say that for u, v of type Vertex that u = v when u.name = v.name. Lean apparently has the machinery to be able to check that strings are equivalent, but I don't know how to extend this to a structure. I tried to create a Prop:
def Vertex.eq (u v : Vertex) : Prop :=
u.name = v.name
But then realized that I needed something of a type similar to DecidableEq. So then I tried something like:
instance Vertex.decEq : DecidableEq Vertex :=
fun u v => decEq u.name v.name
but I had problems with decEq and a fuzzy idea of what is going on with DecidableEq. Can anyone help me out and give me some insight into Decidable types in Lean? I didn't seem to find anything about Decidable types in the tutorial that I listed at the beginning.
Kyle Miller (May 15 2023 at 00:00):
There's at least a metaprogram to generate a DecidableEq
instance for you in many cases:
structure Vertex :=
(name : String)
deriving DecidableEq
#synth DecidableEq Vertex
-- Succeeds, and it says this is the instance:
-- fun a b ↦ instDecidableEqVertex a b
Kyle Miller (May 15 2023 at 00:09):
Something you should know about equality is that every type comes with an equality relation. It's not something you have to define yourself. In fact, the Vertex.eq
you defined is equivalent to the usual one:
def Vertex.eq (u v : Vertex) : Prop :=
u.name = v.name
theorem Vertex.eq_iff (u v : Vertex) :
u = v ↔ Vertex.eq u v :=
by cases u; cases v; simp [Vertex.eq]
Jonathan Whitehead (May 15 2023 at 00:11):
Kyle Miller said:
There's at least a metaprogram to generate a
DecidableEq
instance for you in many cases:structure Vertex := (name : String) deriving DecidableEq #synth DecidableEq Vertex -- Succeeds, and it says this is the instance: -- fun a b ↦ instDecidableEqVertex a b
Well, that seems like a nice little feature. But what if the struct had more fields and equality was only determined by one of them? Is there same way to specify which field constitutes equality?
Kyle Miller (May 15 2023 at 00:12):
Maybe a way to understand the built-in equality relation is what's more-or-less it's definition: "u = v
if and only if whenever something is true for u
then it is also true when v
is substituted for u
". This is a nice definition mathematically, but it doesn't come with any sort of way to check whether u = v
holds -- you have to find some statement whether either substitution succeeds or fails.
Kyle Miller (May 15 2023 at 00:13):
This is where decidability comes in. A proposition p
is called decidable if there's some program that you can evaluate that tells you whether the proposition is True
or False
. A type has decidable equality if for all u
and v
the proposition u = v
is decidable.
Kyle Miller (May 15 2023 at 00:16):
Jonathan Whitehead said:
But what if the struct had more fields and equality was only determined by one of them?
If you want to adjust the definition of equality for a given type, that's what quotient types are for. You can use Quot
to make more things equal (by for example forgetting about everything but a particular field).
Jonathan Whitehead (May 15 2023 at 00:18):
Kyle Miller said:
This is where decidability comes in. A proposition
p
is called decidable if there's some program that you can evaluate that tells you whether the proposition isTrue
orFalse
. A type has decidable equality if for allu
andv
the propositionu = v
is decidable.
This is what I thought was going on. I was trying to define the program to evaluate the proposition but it appears that deriving DecidableEq
does it for you.
Jonathan Whitehead (May 15 2023 at 00:19):
Kyle Miller said:
Jonathan Whitehead said:
But what if the struct had more fields and equality was only determined by one of them?
If you want to adjust the definition of equality for a given type, that's what quotient types are for. You can use
Quot
to make more things equal (by for example forgetting about everything but a particular field).
Great, is there some kind of source for this? Or do I just need to keep digging into the repo to figure out how to use a lot of this stuff? What do you recommend?
Jonathan Whitehead (May 15 2023 at 00:25):
As a working example of how to use quotient types consider:
structure Edge :=
(name : String)
(edge : Vertex × Vertex)
For edges (u,v) and (x,y), we want to say that the edges are equal when u=x and v=y, but we don't really care if they have the same labels. Is there some modification of `deriving DecidableEq
` that would allow me to do this?
Kyle Miller (May 15 2023 at 00:26):
I probably learned most things by using Zulip search (it's got a lot of "unwritten documentation"), looking at the Lean 3 documentation, #tpil, digging through source, and working on mathlib.
Jonathan Whitehead (May 15 2023 at 00:27):
Oh, thanks! I didn't even think to search Zulip, I've just been digging through the git repo for Mathlib.
Kyle Miller (May 15 2023 at 00:28):
Jonathan Whitehead said:
Is there some modification of
`deriving DecidableEq
` that would allow me to do this?
No -- if two Edge
s have different labels, then they are distinguishable, so they're not equal.
Kyle Miller (May 15 2023 at 00:29):
Could we #xy a bit here? You can't mess with equality, but you can definitely create your own relations. What do you want to do with them? Prove things? or write programs that compute using the truth value?
Kyle Miller (May 15 2023 at 00:30):
There's also BEq
with the ==
relation, which is Bool
-valued rather than Prop
-valued, and you're free to make it work however you want. You just don't get much in the way of proofs.
Jonathan Whitehead (May 15 2023 at 00:32):
Kyle Miller said:
Could we #xy a bit here? You can't mess with equality, but you can definitely create your own relations. What do you want to do with them? Prove things? or write programs that compute using the truth value?
Let's suppose that we want to be able to build programs/algorithms and then prove that they're correct. So I suppose we would want to do both, prove things about them and write programs.
Jonathan Whitehead (May 15 2023 at 00:33):
It seems like you're saying that what I was suggesting to do would create more problems than it solves.
Kyle Miller (May 15 2023 at 00:38):
Here's an example of Quot
(though I'll warn you that usually you'd use Quotient
, which is a more convenient wrapper around Quot
):
structure Vertex :=
(name : String)
structure Edge' :=
(name : String)
(edge : Vertex × Vertex)
def Edge'.eq (e1 e2 : Edge') : Prop := e1.edge = e2.edge
def Edge : Type := Quot Edge'.eq
def Edge.mk (e : Edge') : Edge := Quot.mk Edge'.eq e
theorem Edge.eq_iff (e1 e2 : Edge') :
Edge.mk e1 = Edge.mk e2 ↔ e1.edge = e2.edge := by
sorry -- not so hard, but it'd be one line with Quotient
But this is almost surely not what you want. This Edge
type is equivalent to
structure Edge :=
(edge : Vertex × Vertex)
When we form the quotient with Quot
we said we wanted a type where edges are equal if their edge
fields are equal, and that means that name
is effectively something we no longer have access to. It's not exactly erased, but Lean will prevent us from ever writing any computation that can distinguish Edge
s according to their name
in any way.
Jonathan Whitehead (May 15 2023 at 00:43):
Thanks for the example, I see how Quot
is being used now, but I guess the take-away is that we should take care to define our structs in such a way that we don't have unnecessary fields. With my limited knowledge of these kinds of objects I suppose that I'll have to exercise a little trust in that we typically don't want to do this. Thanks for the help! I learned a lot.
Kyle Miller (May 15 2023 at 00:44):
Jonathan Whitehead said:
It seems like you're saying that what I was suggesting to do would create more problems than it solves.
Lean sort of forces you to take equality seriously. If you can distinguish two terms of a type in any way (i.e., if you can't just substitute one for the other) then they're simply not equal. If two edges have different name labels, then they are distinguishable, so they are not equal. You could either make your own relation (like use BEq
and prove theorems about your definition to make it useful), or you could accept that the name
field is significant, or you could remove the name
field because it would ruin the theory.
Or, if you want the name
field only for pretty printing, say, I think you could do a hack where you use Quot
and then write unsafe
code to extract the name
field while printing. A downside is that then you'll have to deal with quotients, which you shouldn't avoid, but it's easier if you don't have to use a Quot
.
Jonathan Whitehead (May 15 2023 at 00:49):
Kyle Miller said:
Jonathan Whitehead said:
It seems like you're saying that what I was suggesting to do would create more problems than it solves.
Lean sort of forces you to take equality seriously. If you can distinguish two terms of a type in any way (i.e., if you can't just substitute one for the other) then they're simply not equal. If two edges have different name labels, then they are distinguishable, so they are not equal. You could either make your own relation (like use
BEq
and prove theorems about your definition to make it useful), or you could accept that thename
field is significant, or you could remove thename
field because it would ruin the theory.Or, if you want the
name
field only for pretty printing, say, I think you could do a hack where you useQuot
and then writeunsafe
code to extract thename
field while printing.
Yeah, I was thinking of the name field as being something that's just for user friendly support like printing. But in my own curiosity I just imagined that there would be structs that we would want to call "equal" in a certain "context". Like, maybe a struct for a Point with the two fields "label" and "(x,y,z)". We might have point A and point B with (1,2,3) and (1,2,4). We might want to make some equality statement about how these two points coincide (are equal in some sense) in a plane. It seems like you would either use the Quot type or you would just define a new struct for (x,y) coordinates and some sort of framework that forgets the third parameter of (x,y,z).
Kyle Miller (May 15 2023 at 00:49):
Jonathan Whitehead said:
I guess the take-away is that we should take care to define our structs in such a way that we don't have unnecessary fields.
Yeah, part of the design work you need to do in formalization is to make sure equality is what you think it is. (For example, SimpleGraph.Subgraph
took a couple of iterations before there was exactly one subgraph per Subgraph
. If I remember correctly, one version allowed vertices outside the subgraph to be adjacent, which doesn't matter if you're only working with vertices in the subgraph, but then the cardinality of Subgraph
is wrong.)
Kyle Miller (May 15 2023 at 00:51):
Jonathan Whitehead said:
It seems like you would either use the Quot type or you would just define a new struct for (x,y) coordinates and some sort of framework that forgets the third parameter of (x,y,z).
Yeah, you don't have to literally use Quot
to form quotient types. The best kind of quotient is when you can write down a convenient type that's equivalent to the quotient. (Quotients in math are defined using universal properties after all, and not by a particular construction.)
Kyle Miller (May 15 2023 at 00:52):
But it's worth knowing that at runtime, a Quot
is still the original type, so all the additional "forgotten" data is still there.
Kyle Miller (May 15 2023 at 00:54):
It's just that the surrounding theory prevents you from making use of it, in the sense that you have to keep proving that functions you define on a quotient give the same result even if you substitute one value for another that's related by your new equality relation (see Quot.lift
)
Kyle Miller (May 15 2023 at 01:01):
Here's a DecidableEq
instance by the way:
structure Vertex :=
(name : String)
instance Vertex.decEq : DecidableEq Vertex :=
fun u v =>
if h : u.name = v.name then
isTrue (by revert h; cases u; cases v; simp)
else
isFalse (by intro h'; subst_vars; exact h rfl)
Kyle Miller (May 15 2023 at 01:01):
think of isTrue
and isFalse
being fancy booleans that come with a proof of their truth
Kyle Miller (May 15 2023 at 01:01):
(at runtime, this proof is erased, but at proving time it's important for correctness)
Kyle Miller (May 15 2023 at 01:03):
if
statements are one of the main interfaces to use Decidable
instances. There's already a DecidableEq String
instance, so I'm able to do this if
without Lean complaining. The h :
notation is a way to grab ahold of a proof or disproof of the condition
Jonathan Whitehead (May 15 2023 at 01:08):
Kyle Miller said:
Here's a
DecidableEq
instance by the way:structure Vertex := (name : String) instance Vertex.decEq : DecidableEq Vertex := fun u v => if h : u.name = v.name then isTrue (by revert h; cases u; cases v; simp) else isFalse (by intro h'; subst_vars; exact h rfl)
I actually had a very close implementation of this but for isTrue
I couldn't figure out the appropriate tactics. I needed revert h
. I felt like I was way off-base and ultimately abandoned finding the correct tactics.
Jonathan Whitehead (May 15 2023 at 01:10):
I also found it weird that within the proof, Lean didn't recognize refl. But in the Natural Number game, you use refl. Is this a new change?
Jonathan Whitehead (May 15 2023 at 01:10):
Also, I tried to use rw h but Lean complained and made me use rw [h].
Kyle Miller (May 15 2023 at 01:11):
(You don't really need revert
here. I just know it'll set up simp
to be able to finish it off quick. That way I don't need simp at h; simp [h]
or something like that.)
Kyle Miller (May 15 2023 at 01:12):
Lean 3 -> Lean 4 has refl
-> rfl
and rw h
-> rw [h]
Kyle Miller (May 15 2023 at 01:13):
Outside of tactics, refl
takes an explicit argument, vs rfl
which doesn't.
#check refl -- ∀ {α : Type u} {r : α → α → Prop} [inst : IsRefl α r] (a : α), r a a
#check rfl -- ∀ {α : Sort u} {a : α}, a = a
Eric Wieser (May 15 2023 at 08:05):
Kyle Miller said:
Or, if you want the
name
field only for pretty printing, say, I think you could do a hack where you useQuot
and then writeunsafe
code to extract thename
field while printing. A downside is that then you'll have to deal with quotients, which you shouldn't avoid, but it's easier if you don't have to use aQuot
.
You can remove most of this pain with
structure Edge where
name : Trunc String
edge : Vertex × Vertex
Eric Wieser (May 15 2023 at 08:06):
Here the quotient is around just one field of the structure rather than the whole thing.
Last updated: Dec 20 2023 at 11:08 UTC