Zulip Chat Archive

Stream: maths

Topic: equiv_like extending fun_like


Jireh Loreaux (Aug 18 2022 at 22:04):

@Anne Baanen Why doesn't equiv_like extend fun_like? I mean, I know why it can't with the current definition (because the the coe_injective is different). But it seems that this is not required because the second argument of equiv_like.coe_injective can be derived from everything else, so it is redundant in the implicaiton. See below. Changing this would allow all the equiv_type classes to be just extensions of equiv_like as well as the corresponding fun_like instances for whatever morphisms we care about.

I was led to this from the consideration about alg_equiv_class that I mentioned before in this thread: https://leanprover.zulipchat.com/#narrow/stream/113488-general/topic/redefining.20alg_equiv_class. Namely, I really wanted to simplify the typeclass assumptions to has_{mul,add,smul} on both the source and the target. If we had a smul_equiv_class, I could do that, but we don't, we only have a smul_hom_class. (Maybe we should have a smul_equiv_class, but that's beside the point.) So, I tried smul_hom_class and that led to an error because I can't extend both smul_hom_class and ring_equiv_class since equiv_like and fun_like have different coe_injective fields.

import data.fun_like.equiv
import tactic

variables {E α β : Type*} [equiv_like E α β]
example (e₁ e₂ : E) (h : (equiv_like.coe e₁ : α  β)  = (equiv_like.coe e₂)) : (equiv_like.inv e₁ : β  α) = equiv_like.inv e₂ :=
begin
  funext,
  have h₁ := @equiv_like.left_inv E α β _ e₂,
  have h₂ := @equiv_like.right_inv E α β _ e₁,
  specialize h₂ x,
  nth_rewrite 0 h at h₂,
  nth_rewrite 1 h₂,
  rw h₁ _,
end

Anne Baanen (Aug 19 2022 at 08:07):

I recall my reasoning was that docs#equiv_like.coe_injective' is much easier to prove in practice: by { intros f g h₁ h₂; cases f; cases g; congr'}. Not allowing diamond inheritance is annoying indeed, so if you know a nice way to keep proofs of coe_injective easy then I'm not going to object to changing this.

Anne Baanen (Aug 19 2022 at 08:10):

Note however that docs#ring_equiv_class is not just docs#ring_hom_class with an extra inverse: bijective maps automatically preserve zero and one, so we don't include map_zero and map_one fields. So I don't know how many cases the diamond inheritance actually helps with. (Similarly for alg_equiv_class.)

Eric Wieser (Aug 19 2022 at 08:11):

Why do we case whether equiv_like extends fun_like when we already have docs#equiv_like.fun_like? oh, we don't have that

Anne Baanen (Aug 19 2022 at 08:12):

We do, it just goes via docs#equiv_like.to_embedding_like :)

Anne Baanen (Aug 19 2022 at 08:13):

The specific thing that we don't have is being able to say extends foo_equiv_class, bar_hom_class.

Jireh Loreaux (Aug 19 2022 at 14:01):

I think one way to keep the equiv_like proofs easy is just to have a custom constructor for equiv_like which eats the current version and spits out the new version with the fun_like coe_injective field. Of course, we probably wouldn't want to have such a constructor for every extension of equiv_like, so this may lead to slightly ugly proofs like the one below. And of course this isn't quite as intuitive, so we would need to add something to the documentation to the effect of "always use the custom constructor when creating instances."

instance : add_equiv_class F A B :=
{ map_add := sorry,
  .. custom_constructor
  { coe := sorry,
    inv := sorry,
    left_inv := sorry,
    right_inv := sorry,
    coe_injective := sorry } }

Jireh Loreaux (Aug 19 2022 at 14:05):

does this seem reasonable?

Jireh Loreaux (Aug 19 2022 at 14:16):

I just saw your code note in equiv_like about your reasoning for including the inv in the coe_injective field, sorry!

Jireh Loreaux (Aug 19 2022 at 15:15):

This is what it would look like:

import data.fun_like.basic
import tactic.congr

-- this would be the basic setup

variables {E α β : Type*}

structure equiv_like.core (E α β : Type*) :=
(coe : E  α  β)
(inv : E  β  α)
(left_inv  :  e, function.left_inverse (inv e) (coe e))
(right_inv :  e, function.right_inverse (inv e) (coe e))
-- The `inv` hypothesis makes this easier to prove with `congr'`
(coe_injective' :  e g, coe e = coe g  inv e = inv g  e = g)

lemma equiv_like.inv_eq_of_coe_eq (h : equiv_like.core E α β) {e₁ e₂ : E}
  (h_eq : h.coe e₁ = h.coe e₂) : h.inv e₁ = h.inv e₂ :=
funext $ λ x, h.left_inv e₂ (h.inv e₁ x)  (congr_arg (h.inv e₂) (h_eq  h.right_inv e₁ x))

class equiv_like (E : Type*) (α β : out_param Type*) extends fun_like E α (λ _, β) :=
(inv : E  β  α)
(left_inv  :  e, function.left_inverse (inv e) (coe e))
(right_inv :  e, function.right_inverse (inv e) (coe e))

variables (E)
def equiv_like.of_core (h : equiv_like.core E α β) : equiv_like E α β :=
{ coe := h.coe,
  coe_injective' := λ e₁ e₂ he, h.coe_injective' _ _ he (equiv_like.inv_eq_of_coe_eq h he),
  inv := h.inv,
  left_inv := h.left_inv,
  right_inv := h.right_inv }

----- then an example to see how it works in practice

class add_equiv_class (E : Type*) (α β : out_param Type*) [has_add α] [has_add β]
  extends equiv_like E α β :=
(map_add :  e : E,  x y : α, e (x + y) = e x + e y)

structure add_equiv (α β : Type*) [has_add α] [has_add β] :=
(to_fun : α  β)
(inv_fun : β  α)
(left_inv : function.left_inverse inv_fun to_fun)
(right_inv : function.right_inverse inv_fun to_fun)
(map_add' :  x y, to_fun (x + y) = to_fun x + to_fun y)

instance add_equiv.add_equiv_class {α β : Type*} [has_add α] [has_add β] :
  add_equiv_class (add_equiv α β) α β :=
{ map_add := λ f, f.map_add',
  .. (equiv_like.of_core (add_equiv α β)
  { coe := λ e, e.to_fun,
    coe_injective' := λ e₁ e₂ h₁ h₂, by { cases e₁; cases e₂; congr' },
    inv := λ e, e.inv_fun,
    left_inv := λ e, e.left_inv,
    right_inv := λ e, e.right_inv, }) }

Jireh Loreaux (Aug 19 2022 at 17:50):

Another way, almost certainly better: rename the current equiv_like.coe_injective' field to equiv_like.coe_inv_injective, and then have autofill the equiv_like.coe_injective' (with the fun_like.coe_injective' definition) with the necessary lemma.

Jireh Loreaux (Aug 19 2022 at 17:53):

(deleted)

Frédéric Dupuis (Aug 19 2022 at 18:11):

Why not just have a lemma that proves fun_like.coe_injective' from the current equiv_like.coe_injective', that we would then systematically apply, instead of doing this with a whole structure?

Frédéric Dupuis (Aug 19 2022 at 18:13):

Also, if we make equiv_like extend fun_like, my guess is that the coe_injective' proof would almost always come from the corresponding hom anyway, so we would almost never actually have to prove it "by hand".

Yaël Dillies (Aug 19 2022 at 18:14):

What are you trying to achieve, Jireh? I don't understand how this has to do with what class extends what.

Jireh Loreaux (Aug 19 2022 at 18:25):

Frédéric Dupuis said:

Also, if we make equiv_like extend fun_like, my guess is that the coe_injective' proof would almost always come from the corresponding hom anyway, so we would almost never actually have to prove it "by hand".

No, Anne has a point here, it would be (comparatively) hard to prove the fun_like.coe_injective' field when creating an instance of equiv_like for some kind of equiv type. (if you don't understand, try it directly and you'll realize why it's hard.)

Jireh Loreaux (Aug 19 2022 at 18:26):

Yaël Dillies said:

What are you trying to achieve, Jireh? I don't understand how this has to do with what class extends what.

The point is to make equiv_like extend fun_like, and that way you can write things like class new_equiv_class extends old_equiv_class, some_hom_class, which we currently can't do.

Jireh Loreaux (Aug 19 2022 at 18:26):

Give me a few minutes and I'll have something nice working that should be almost a drop-in replacement.

Yaël Dillies (Aug 19 2022 at 18:27):

Okay, but why do you care about the extension mechanism? This is an implementation detail, even from the point of view of API writing!

Jireh Loreaux (Aug 19 2022 at 18:30):

I'm not sure how what I just said doesn't explain why I care. Yes, of course this is an implementation detail, but the point is I'm looking to improve the implementation slightly.

Yaël Dillies (Aug 19 2022 at 18:32):

I am trying to un-xy, and you just gave me another x!

Jireh Loreaux (Aug 19 2022 at 18:33):

Read the second paragraph of the first post in the thread. If that doesn't explain it, then I'm not sure why we're not understanding each other.

Yaël Dillies (Aug 19 2022 at 18:34):

So, I tried smul_hom_class and that led to an error because I can't extend both smul_hom_class and ring_equiv_class since equiv_like and fun_like have different coe_injective fields.

Why can't you copy over the fields? This is the standard procedure when you can't use extends.

Jireh Loreaux (Aug 19 2022 at 18:36):

Of course you can! I don't want to. I want things to Just Workᵀᴹ. This is just one small way to make that happen.

Jireh Loreaux (Aug 19 2022 at 18:36):

It's certainly not a dire point.

Jireh Loreaux (Aug 19 2022 at 18:45):

I'm not sure why this is broken, presumably it has something to do with the show, but if I could fix whatever the issue here is, this change would essentially amount to a renaming of coe_injective' to coe_inv_injective' wherever an instance of some_equiv_class is declared.

import data.fun_like.basic
import tactic.congr

variables {E α β : Type*}

open function

lemma equiv_like.coe_injective_of_coe_inv (coe' : E  α  β) (inv' : E  β  α)
  (hl :  e, left_inverse (inv' e) (coe' e))
  (hr :  e, right_inverse (inv' e) (coe' e))
  (h :  e₁ e₂, coe' e₁ = coe' e₂  inv' e₁ = inv' e₂  e₁ = e₂) :
  injective coe' :=
λ e₁ e₂ he, h e₁ e₂ he $ funext $ λ x, hl e₂ (inv' e₁ x)  congr_arg (inv' e₂) (he  hr e₁ x)

class equiv_like (E : Type*) (α β : out_param Type*) extends fun_like E α (λ _, β) :=
(inv : E  β  α)
(left_inv  :  e, function.left_inverse (inv e) (coe e))
(right_inv :  e, function.right_inverse (inv e) (coe e))
(coe_inv_injective' :  e g, coe e = coe g  inv e = inv g  e = g)
(coe_injective' := equiv_like.coe_injective_of_coe_inv (show E  α  β, from coe) inv left_inv
  right_inv coe_inv_injective')

structure equiv (α β : Type*) :=
(to_fun : α  β)
(inv_fun : β  α)
(left_inv : function.left_inverse inv_fun to_fun)
(right_inv : function.right_inverse inv_fun to_fun)

instance equiv.equiv_class {α β : Type*} :
  equiv_like (equiv α β) α β :=
{ coe := λ e, e.to_fun,
  inv := λ e, e.inv_fun,
  left_inv := λ e, e.left_inv,
  right_inv := λ e, e.right_inv,
  coe_inv_injective' := sorry, }

Jireh Loreaux (Aug 19 2022 at 18:49):

Any help fixing it is appreciated, although it seems like I'm the only one who values this! :rofl:

Jireh Loreaux (Aug 19 2022 at 19:30):

okay, I would actually really appreciate if someone can debug the code immediately above (I have edited it slightly), because it feels like an actual bug in Lean to me, but I know virtually nothing. Note that with pp.all true the two types that it is complaining about are verbatim the same; I know for certain because I copied them to two files and diffed them.

Jireh Loreaux (Aug 19 2022 at 20:13):

cc @Mario Carneiro, is this an actual bug, or am I just really stupid somehow?

Jireh Loreaux (Aug 19 2022 at 20:16):

sorry, I just noticed there is also this error, but I'm not sure why it's happening: Error updating: deep recursion was detected at 'expression replacer' (potential solution: increase stack space in your system).

Reid Barton (Aug 19 2022 at 20:16):

I don't know what to make of the fact that ?coe_inv_injective' appears in that type

Jireh Loreaux (Aug 19 2022 at 20:18):

In case you didn't read the whole thread (expected), this almost certainly has something to do with line 21, note that coe_injective' is a field of fun_like, the class that this equiv_like is extending. Have I done something we're not allowed to do?

Mario Carneiro (Aug 19 2022 at 20:22):

yeah I think reid found the bug. The type of the metavariable refers to the metavariable itself, so you can't assign it

Mario Carneiro (Aug 19 2022 at 20:23):

if you put something other than sorry there, or remove it, or type ascribe it the error may go away

Jireh Loreaux (Aug 19 2022 at 20:26):

with the additional import tactic.congr and replacing the sorry by the usual proof that goes here, namely: λ e g h₁ h₂, by { cases e; cases g; congr' }, then this fails with

tactic failed, result contains meta-variables
state:
no goals

Eric Rodriguez (Aug 19 2022 at 20:29):

what does recover do?

Jireh Loreaux (Aug 19 2022 at 20:31):

Same type mismatch as before, although this time without the deep recursion problem. The metavariable ?coe_inv_injective' still appears in the type.

Jireh Loreaux (Aug 19 2022 at 20:32):

Is this just something we're not capable of doing (i.e., assigning default values to fields of structures we're extending)?

Yaël Dillies (Aug 19 2022 at 20:33):

It is definitely possible, but I've encountered problems doing so myself.

Jireh Loreaux (Aug 19 2022 at 20:42):

Ha! fixed with set_option old_structure_command true, which I probably wanted anyway. :face_palm:

Jireh Loreaux (Aug 19 2022 at 21:20):

The real fix is not extracting that lemma equiv_like.coe_injective_of_coe_inv.

Jireh Loreaux (Aug 20 2022 at 19:58):

#16161 is essentially a drop-in replacement for equiv_like.

Eric Wieser (Aug 22 2022 at 20:51):

And of course this isn't quite as intuitive, so we would need to add something to the documentation to the effect of "always use the custom constructor when creating instances."

But conversely, users of {! !} will now be prompted to provide both versions of the coe_injective field; so the new version will also need something in the documentation explaining that coe_injective should not be provided explicitly.

Yaël Dillies (Aug 22 2022 at 20:53):

In fact, I've been doing the reverse change in other structures throughout the library: If making A extend B results in a redundant field, do not extend B and write the instance manually.

Eric Wieser (Aug 22 2022 at 20:55):

@Jireh Loreaux, do you have an example based on #16161 that demonstrates a situation where class new_equiv_class extends old_equiv_class, some_hom_class is useful? I note that you don't change any of the existing typeclasses to use this spelling; was that to keep the PR small, or because none of the existing typeclasses fit into this pattern?

Yaël Dillies (Aug 22 2022 at 20:55):

And there are many common examples as well. Eg docs#group doesn't extend docs#cancel_monoid but instead we have the manual docs#group.to_cancel_monoid. Or again docs#ring doesn't extend docs#semiring.

Eric Wieser (Aug 22 2022 at 20:57):

I think the difference here is that those all share a common has_mul ancestor though, so you can still extend them all at once later without duplicate field name conflicts.

Eric Wieser (Aug 22 2022 at 20:58):

We don't have a barely_fun_like ancestor to share the coe field of equiv_like and fun_like

Eric Wieser (Aug 22 2022 at 20:58):

If we did, then I think Jireh's problem would be solvable using renaming to solve the conflict in the non-data fields

Yaël Dillies (Aug 22 2022 at 20:59):

Eric Wieser said:

I think the difference here is that those all share a common has_mul ancestor though, so you can still extend them all at once later without duplicate field name conflicts.

But we never do! because that would still result in redundant fields, however far in the hierarchy you're going.

Eric Wieser (Aug 22 2022 at 21:00):

But at least in user code, you could do that. You're right though, the cases probably doesn't come up in mathlib

Yaël Dillies (Aug 22 2022 at 21:01):

I maintain that whether a typeclass extends another or not is an implementation detail, even from the implementation perspective.

Yaël Dillies (Aug 22 2022 at 21:02):

The tradeoff here is "easeness to extend structure A" vs "easeness to provide instances/definitions of structure A". Most likely, the latter is more common.

Jireh Loreaux (Aug 22 2022 at 21:04):

To answer the @ mention: the example that caused me to look at this in the first place was redefining alg_equiv_class so that it works for both unital and non-unital (even non-associative) algebras. For this it should (or could) be extends ring_equiv_class, smul_hom_class.

Jireh Loreaux (Aug 22 2022 at 21:05):

I tried to do that, it didn't work, I went hunting and realized it should be possible to make equiv_like extend fun_like, hence this PR.

Jireh Loreaux (Aug 22 2022 at 21:05):

Yaël, I contend that it's just as easy to provide instances now as it was before. I made sure that this would be a drop-in replacement.

Jireh Loreaux (Aug 22 2022 at 21:06):

And Eric, I'm not sure of other examples, but yes, I was trying to keep the PR small and make sure nothing really worked differently.

Jireh Loreaux (Aug 22 2022 at 21:10):

Eric Wieser said:

But conversely, users of {! !} will now be prompted to provide both versions of the coe_injective field; so the new version will also need something in the documentation explaining that coe_injective should not be provided explicitly.

I thought I had this explanation in the PR already; :thinking: maybe I git stashed that documentation somewhere.

Yaël Dillies (Aug 22 2022 at 21:16):

I still feel like this is a move in the wrong direction. Many less people will try extending equiv_like than there will be people proving equiv_like instances.

Jireh Loreaux (Aug 22 2022 at 21:25):

To me, extending it makes more sense, despite the fact that I concede your contention that instances are more common than extensions. I fully expected equiv_like to extend fun_like because I think of it has some_fun_like + an inverse, where the fun_like assumptions that go in the equiv_like are as weak as possible, not necessarily the one for the hom class.

But look, I agree it's just an implementation detail. It's one that I quite like to have this way, but I am content to be overruled as well. It's a collaboration after all, so if people don't prefer it then that's fine.

Yaël Dillies (Aug 22 2022 at 21:35):

I fully expected equiv_like to extend fun_like because I think of it has some_fun_like + an inverse

Actually, this pattern basically never holds:

Maybe your expectation comes from this set-theoretic idea of using bijections, rather than equivalences?

Jireh Loreaux (Aug 22 2022 at 21:40):

Yaël, that's not what I meant. I mean that I think of mul_equiv_class as mul_hom_class + equiv_like. I think of ring_equiv_class as mul_hom_class + add_equiv_class, etc. I do not think of them as extending the corresponding hom, only the hom which is the "right" one. I know that the pattern you described doesn't hold; hence my desire to redefine alg_equiv_class in the first place.

Jireh Loreaux (Aug 22 2022 at 21:42):

The point is: we only ever assume properties about the coe function, not the inv (aside from the fact that it's an inverse).

Yaël Dillies (Aug 22 2022 at 21:43):

Wrong: docs#homeomorph

Jireh Loreaux (Aug 22 2022 at 21:46):

aha, touché. That is the canonical example after all, I should have been more careful.

Jireh Loreaux (Aug 22 2022 at 21:49):

In any case, if people don't want it, then we just don't merge it. You're welcome to set up a poll if you wish.

Frédéric Dupuis (Aug 22 2022 at 22:16):

I don't follow the argument against this change. I can see this being not very useful and not worth the effort in the worst case, but since the work is already done, why not?

Yaël Dillies (Aug 22 2022 at 22:17):

As I said above, I already performed the analogous change in the opposite direction for other classes, like docs#boolean_algebra.

Anne Baanen (Aug 24 2022 at 13:01):

Perhaps the conclusion we should be drawing from this discussion is that the Hierarchy Builder people are onto something with their notion of Factory (which corresponds in Lean to something like the {..s} notation for defining a structure).

Jireh Loreaux (Aug 24 2022 at 13:08):

Sorry, I don't understand that comment. Can you elaborate?

Anne Baanen (Aug 24 2022 at 13:12):

The problem we're kind of running into here is that we have no good way to customize the relation between fun_likes and equiv_likes, because the built-in structure syntax { inv := inv, .. (by apply_instance : fun_like whatever) } doesn't work. Hierarchy Builder's factories by default work something like Lean's built-in structure syntax but can be overridden to do the right thing here.


Last updated: Dec 20 2023 at 11:08 UTC