Zulip Chat Archive

Stream: mathlib4

Topic: advice for defining bundled hom and equiv structures


Edward van de Meent (Feb 27 2024 at 17:11):

i'd like to define bundled hom and equiv structures for Codes (which i have defined in my personal branch), and i'd like to know how i should go about extending existing structs, like Equiv and such...
looking through how this was done for other structures, i have some specific questions i'd like to know the answer to, but general advice is also welcome.

  1. Looking at LinearEquiv, it doesn't seem to explicitly require σ or σ' to be a ring-isomorphism, rather requiring them to be of type R →+* S and S →+* R respectively, and having parameters RingHomInvPair σ σ' and RingHomInvPair σ' σ, both of which contain fields stating that composing them either way gives id on either ring. why is this defined like this?

  2. Looking at the same, i noticed that in the code, it extends M ≃+ M₂, while the snippet in the documentation doesn't... why is this?

  3. It extends both LinearMap σ M M₂ and M ≃+ M₂. The left inheritance reveals a structure AddHom, and so does the right. there doesn't appear to be any field stating the values of these structures are the same... It is my understanding that if this were a class, this would lead to diamond issues, but because this is a structure, it doesn't. However, there is no field stating these fields are the same either. Does lean know these need to be the same by itself, or is this specified somewhere I overlooked?

  4. Similarly, RingEquiv extends R ≃ S,R ≃* S and R ≃+ S (but only the first is mentioned in the documentation)... If lean knows to unify the R ≃ S field, why is it needed to explicitly state it as an extension in the code? Why doesn't it only extend R ≃* S and R ≃+ S?

  5. If lean knows to unify these fields, why can't the same be done with classes? Why doesn't lean solve diamond issues by itself?

Anne Baanen (Feb 28 2024 at 11:04):

  1. I believe this is so that turning a linear equivalence (not semilinear equivalence) into a semilinear map gives an actual linear map: RingHom.id instead of (RingEquiv.refl _).toRingHom. We are having similar issues in #6057 where Lean expects MonoidHom.id but gets (RingHom.id _).toMonoidHom.
  2. You mean in the autogenerated declaration at docs#LinearEquiv it doesn't mention extends AddEquiv? I'm not sure why that happens, maybe the diamond inheritance that you mention? In most cases, it is much better to follow the actual code than the autogenerated documentation.
  3. There is in fact diamond inheritance, but for both structures and classes this is not an issue. What happens under the hood is that a structure like LinearEquiv will look like a field toLinearMap containing everything from LinearMap and then separately all the fields that are in AddEquiv but not in LinearMap: invFun, left_inv and right_inv. When you want to apply LinearEquiv.toAddEquiv, under the hood it unpacks the required fields from toLinearMap and adds the remaining separate fields.
  4. This is probably done to remain neutral between additive and multiplicative structure: under the hood a RingEquiv will have three fields: toEquiv, map_add' and map_mul'. If we wrote RingHom extends MulEquiv, AddEquiv then we get two asymmetrical fields: toMulEquiv and map_add'. It seems neater this way, although I don't have any technical reason that we need do this or something will break.
  5. It does! To be technical, as long as your diamond inheritance is forgetful there will be no problems. When you use extends, Lean ensures this is the case. A diamond in the typeclass hierachy becomes a problem once you create data out of existing fields. The big example is scalar multiplication by a natural number: in any additive monoid A, we could define n • a := a + a + ... + a, n times. In any multiplicative monoid M, we could define a • b := a * b. So what if A = M = ℕ? Then we have to arrange these multiplications to coincide, which we do in Mathlib by not fixing the definition n • a = a + a + ... + a but instead adding this as an extra axiom.

Notification Bot (Feb 28 2024 at 13:58):

Edward van de Meent has marked this topic as resolved.

Notification Bot (Mar 03 2024 at 17:43):

Edward van de Meent has marked this topic as unresolved.

Edward van de Meent (Mar 03 2024 at 17:45):

is there somewhere i could find a list of definitions/theorems i should implement for bundled Homs/ Equivs (and their respective classes)? i'm thinking of things like .id, .rfl, .trans, etc?

Edward van de Meent (Mar 05 2024 at 10:13):

i am tempted to declare classes HomClass and EquivClass, because i think that is basically the pattern that we're implementing each time...
is it possibly the case that i want to use that objects and their Homs form a category?

Edward van de Meent (Mar 05 2024 at 10:14):

and something similar for Equiv types?

Edward van de Meent (Mar 05 2024 at 10:17):

(i tried declaring the classes and ran into universe errors which i have no experience with solving, which tipped me off to this being some kind of category theory concept)

Johan Commelin (Mar 05 2024 at 10:46):

Yup, those universe problems are known, and they can't really be solved.

Johan Commelin (Mar 05 2024 at 10:46):

One solution would be to have some meta-code that can generate all these id, rfl, trans, etc... declarations.

Johan Commelin (Mar 05 2024 at 10:46):

But that's a substantial project

Edward van de Meent (Mar 05 2024 at 10:48):

a nice start would be to have a list of declarations and proofs, to at least make the convention documented somewhere

Edward van de Meent (Mar 06 2024 at 19:29):

i made a start with extracting the general API...
i have created a generic structure MyHom which extends structures XHom and YHom, where YHom has the same parameters as MyHom, while XHom has one less.

definitions and lemmas for XHom and YHom

definitions and lemmas for MyHom

Edward van de Meent (Mar 06 2024 at 19:33):

there is also MyHomClass in there.
if anyone is missing some lemmas/definitions, please say so

Edward van de Meent (Mar 06 2024 at 19:34):

i ported all of these from Mathlib.Algebra.Group.Hom .Defs, if anyone wants to know

Edward van de Meent (Mar 06 2024 at 19:35):

i'll see if i can expand this to also include Equiv versions and the like

Edward van de Meent (Mar 06 2024 at 19:43):

some of these lemmas are proven using the fact that MyHom only adds Prop-valued fields. other than that, there are still some other lemmas using sorry.
those are:

  • MyHom.id, where you need to provide values for all extra fields of MyHom compared to its parents, such as proofs that id has the properties that MyHom describes
  • MyHom.comp, where you need to provide those values too, such as proofs that composition preserves the properties that MyHom describes

Edward van de Meent (Mar 06 2024 at 19:50):

if you are adding other non-prop fields, however, that will make it all a tad bit more complicated... for instance, the proof of cancel_left depends on the FunLike.coe_injective' field, while comp_assoc is currently proven rfl

Edward van de Meent (Mar 06 2024 at 19:52):

although honestly i have yet to think of an example where that is relevant

Edward van de Meent (Mar 07 2024 at 18:03):

I'm implementing my own Isometry structure, and i ran into the following:
The docs in Mathlib.Data.FunLike.Equiv say i should implement .copy for my Equiv as follows:

protected def copy (f : MyIso A B) (f' : A  B) (f_inv : B  A) (h : f' = f) : MyIso A B :=
  { toFun := f',
    invFun := f_inv,
    left_inv := h.symm  f.left_inv,
    right_inv := h.symm  f.right_inv,
    map_op' := h.symm  f.map_op' }

However, the example provided doesn't work, as you also need f_inv = f.invFun, or leave f.invFun the same between the instances...
I looked for examples in mathlib where this pattern is implemented, but a quick search didn't result in anything much.
Could someone tell me if this pattern is actually wanted, and if so, where i can find a working example of this API for any isometry-like structure?

Jireh Loreaux (Mar 07 2024 at 18:09):

You don't need them to be equal because they are provably equal.

Edward van de Meent (Mar 07 2024 at 18:09):

are they? f_inv is a generic function B -> A though?

Edward van de Meent (Mar 07 2024 at 18:10):

completely independent from f'

Jireh Loreaux (Mar 07 2024 at 18:12):

oh, sorry, you haven't proved copy, I misunderstood. Yes, you need that hypothesis in order to prove left_inv and right_inv.

Jireh Loreaux (Mar 07 2024 at 18:13):

And you should definitely allow the user to replace the invFun too, because the whole purpose is for providing better definitional properties.

Edward van de Meent (Mar 07 2024 at 18:15):

ok... is there some implementation in mathlib i could look at for inspiration?

Jireh Loreaux (Mar 07 2024 at 18:19):

A PR updating the text in Mathlib.Data.FunLike.Equiv would be appreciated.

Jireh Loreaux (Mar 07 2024 at 18:19):

Without the morphism properties, it just looks like this:

variable {α β : Type*}

def Equiv.copy (e : α  β) (f : α  β) (f_inv : β  α) (hf : f = e) (hf_inv : f_inv = e.symm) :
    α  β where
  toFun := f
  invFun := f_inv
  left_inv := hf_inv.symm  hf.symm  e.left_inv
  right_inv := hf_inv.symm  hf.symm  e.right_inv

Jireh Loreaux (Mar 07 2024 at 18:20):

Does that answer your question? I'm not sure we have very many (any?) of these .copy defs for EquivLike types.

Jireh Loreaux (Mar 07 2024 at 18:20):

(Although it would be convenient if we did)

Edward van de Meent (Mar 07 2024 at 18:21):

yes, thanks! indeed, i haven't found any EquivLike types implementing this.

Edward van de Meent (Mar 07 2024 at 18:36):

it turns out it is a tad trickier when extending other copy lemmas, as apparently lean is unable to rewrite with hf and hf_inv at the inverse lemmas of e. what does consistently work, however, is this:

variable {α β : Type*}

def Equiv.copy (e : α  β) (f : α  β) (f_inv : β  α) (hf : f = e) (hf_inv : f_inv = e.symm) :
    α  β where
  toFun := f
  invFun := f_inv
  left_inv := by
    simp_rw [hf_inv,hf]
    exact f.left_inv
  right_inv := by
    simp_rw [hf_inv,hf]
    exact f.right_inv

Edward van de Meent (Mar 11 2024 at 12:20):

i also ran into some trouble with the suggested way to define MyIsoClass..
defining MyIso.instMyIsoClass as suggested (see section A in MWE) gives an error. my current solution to this is section B, although i do think that's hacky and somewhat ugly...
#mwe :

import Mathlib.Data.FunLike.Equiv
import Mathlib.Logic.Equiv.Defs

variable {α β :Type*}
structure MyHom (α β :Type*) where
  toFun : α  β

class MyHomClass (T:Type*) (α β: outParam Type*) [FunLike T α β]
namespace MyHom
instance instFunLike : FunLike (MyHom α β) α β where
  coe := MyHom.toFun
  coe_injective' := fun f g h => by cases f; cases g; congr

instance instMyHomClass : MyHomClass (MyHom α β) α β where
end MyHom
structure MyEquiv (α β:Type*) extends α  β, MyHom α β where

class MyEquivClass (T:Type*) (α β: outParam Type*) extends EquivLike T α β, MyHomClass T α β

namespace MyEquiv

section A
-- /-
instance instMyEquivClass : MyEquivClass (MyEquiv α β) α β := {-- failed to synthesize instance FunLike (MyEquiv α β) α β
  coe := fun f => f.toFun
  inv := fun f => f.invFun
  left_inv := fun f => f.left_inv
  right_inv := fun f => f.right_inv
  coe_injective' := fun f g h => by cases f; cases g; congr; simp_all
}
-- -/
end A

section B
/-
def instEquivLike : EquivLike (MyEquiv α β) α β := {
  coe := fun f => f.toFun
  inv := fun f => f.invFun
  left_inv := fun f => f.left_inv
  right_inv := fun f => f.right_inv
  coe_injective' := fun f g h => by cases f; cases g; congr; simp_all
}

def instMyHomClass :
  @MyHomClass (MyEquiv α β) α β (@EquivLike.toFunLike
      (MyEquiv α β) α β MyEquiv.instEquivLike) :=
  (@MyHomClass.mk (MyEquiv α β) α β (@EquivLike.toFunLike
    (MyEquiv α β) α β MyEquiv.instEquivLike))


instance instMyEquivClass : MyEquivClass (MyEquiv α β) α β :=
  @MyEquivClass.mk (MyEquiv α β) α β MyEquiv.instEquivLike MyEquiv.instMyHomClass
-/
end B
end MyEquiv

Edward van de Meent (Mar 11 2024 at 12:23):

basically, my current solution is using MyIsoClass.mk with explicit arguments.

Edward van de Meent (Mar 11 2024 at 12:24):

i honestly expected lean to be able to handle version A, but apparently not...

Edward van de Meent (Mar 11 2024 at 12:30):

i found a shorter solution, but i'm still not really happy...

instance instMyEquivClass : MyEquivClass (MyEquiv α β) α β := {({
  coe := fun f => f.toFun
  inv := fun f => f.invFun
  left_inv := fun f => f.left_inv
  right_inv := fun f => f.right_inv
  coe_injective' := fun f g h => by cases f; cases g; congr; simp_all} : EquivLike (MyEquiv α β) α β) with
}

Last updated: May 02 2025 at 03:31 UTC