Zulip Chat Archive
Stream: mathlib4
Topic: Fixing the `erw` hell around concrete categories
Anne Baanen (Oct 09 2024 at 15:22):
It seems that unification hints can help us solve quite a bit of erw
hell in the category theory area of the library: #17583 But it's failing in a few places because CommRingCat
is getting unfolded to Bundled CommRing
when it shouldn't. Anyone have ideas how to fix the regressions?
More details: We have a lot of erw
s and rfl
s in files dealing with concrete categories. This seems to be mostly due to the existence of two casts: ConcreteCategory.hasCoeToSort
and Bundled.coeSort
which both represent the forgetful functor from concrete categories to Type. The first is only a local instance but is needed to state lemmas on these concrete categories, while the second one is always available but only applies to categories defined as Bundled
. The casts are defeq but not reducibly so, causing issues when rewriting with generic lemmas in the context of a specific Bundled
category.
For some specific categories we already have a workaround by @Kim Morrison: a unification hint of the form ⊢ (forget CommRingCat).obj R ≟ R
also fires at reducible transparency. Adding this for any Bundled c
would generalize the fix to a large area of the library. But there are some regressions.
Looking at the Meta.isDefEq
traces didn't help because I can't actually find where the unfolding happens.
Is there a way to avoid the unfolding of CommRingCat
? Can we somehow fix this by setting the right priority? Where does the unfolding even happen?
Anne Baanen (Oct 10 2024 at 07:44):
Note that making docs#CommRingCat an abbrev
doesn't fix the issue directly, since we still get an error failed to synthesize PreservesFilteredColimits (forget (Bundled CommRing))
.
Anne Baanen (Oct 10 2024 at 08:29):
After staring at this for a while, it looks like the problem is indeed that the new unification hint for Bundled
fires instead of the CommRingCat
one. On master
:
[Meta.isDefEq] ✅️ (CategoryTheory.forget ?m.4502).obj (?m.4508.stalk ?m.4509) =?= ↑X.functionField ▼
[] ✅️ (CategoryTheory.forget ?m.4502).obj (?m.4508.stalk ?m.4509) =?= ↑X.functionField ▼
[] ✅️ (CategoryTheory.forget ?m.4502).toPrefunctor.1 (?m.4508.stalk ?m.4509) =?= X.functionField.1 ▼
[onFailure] ✅️ (CategoryTheory.forget ?m.4502).toPrefunctor.1 (?m.4508.stalk ?m.4509) =?= X.functionField.1 ▼
[stuckMVar] found stuck MVar ?m.4506 : CategoryTheory.ConcreteCategory ?m.4502
[isDefEq] 💥️ CategoryTheory.ConcreteCategory ?m.4502 =?= CategoryTheory.ConcreteCategory TopCommRingCat ▶
[hint] (CategoryTheory.forget ?m.4502).toPrefunctor.1 (?m.4508.stalk ?m.4509) =?= X.functionField.1
[hint] ❌️ hint SemiRingCat.forget_obj_eq_coe at (CategoryTheory.forget ?m.4502).toPrefunctor.1
(?m.4508.stalk ?m.4509) =?= X.functionField.1 ▶
[hint] ❌️ hint RingCat.forget_obj_eq_coe at (CategoryTheory.forget ?m.4502).toPrefunctor.1
(?m.4508.stalk ?m.4509) =?= X.functionField.1 ▶
[hint] ❌️ hint CommSemiRingCat.forget_obj_eq_coe at (CategoryTheory.forget ?m.4502).toPrefunctor.1
(?m.4508.stalk ?m.4509) =?= X.functionField.1 ▶
[hint] ✅️ hint CommRingCat.forget_obj_eq_coe at (CategoryTheory.forget CommRingCat).toPrefunctor.1
(X.presheaf.stalk (genericPoint ↑↑X.toPresheafedSpace)) =?= X.functionField.1 ▼
[isDefEq] ✅️ (CategoryTheory.forget CommRingCat).obj
?m.4607 =?= (CategoryTheory.forget ?m.4502).toPrefunctor.1 (?m.4508.stalk ?m.4509) ▶
[isDefEq] ✅️ ↑(?m.4508.stalk ?m.4509) =?= X.functionField.1 ▶
[] CommRingCat.forget_obj_eq_coe succeeded, applying constraints
On my branch:
[] ✅️ (CategoryTheory.forget ?m.4631).toPrefunctor.1 (?m.4637.stalk ?m.4638) =?= X.functionField.1 ▼
[onFailure] ✅️ (CategoryTheory.forget ?m.4631).toPrefunctor.1 (?m.4637.stalk ?m.4638) =?= X.functionField.1 ▼
[stuckMVar] found stuck MVar ?m.4635 : CategoryTheory.ConcreteCategory ?m.4631
[isDefEq] 💥️ CategoryTheory.ConcreteCategory ?m.4631 =?= CategoryTheory.ConcreteCategory TopCommRingCat ▼
[] 💥️ ?m.4631 =?= TopCommRingCat ▼
[] ?m.4631 [nonassignable] =?= TopCommRingCat [nonassignable]
[hint] (CategoryTheory.forget ?m.4631).toPrefunctor.1 (?m.4637.stalk ?m.4638) =?= X.functionField.1
[hint] ✅️ hint CategoryTheory.BundledHom.hint._@.Mathlib.CategoryTheory.ConcreteCategory.BundledHom._hyg.482 at (CategoryTheory.forget
(CategoryTheory.Bundled CommRing)).toPrefunctor.1
(X.presheaf.stalk (genericPoint ↑↑X.toPresheafedSpace)) =?= X.functionField.1 ▼
[isDefEq] ✅️ (CategoryTheory.forget (CategoryTheory.Bundled ?m.4664)).obj
?m.4667 =?= (CategoryTheory.forget ?m.4631).toPrefunctor.1 (?m.4637.stalk ?m.4638) ▶
Anne Baanen (Oct 10 2024 at 08:40):
In particular, this depends on the order that we add the hints: declaring the generic one after the specialized one will make everything work. But we probably shouldn't make our solution order-dependent, since anyone adding a new Bundled
synonym will then start encountering the same issues.
Anne Baanen (Oct 10 2024 at 08:40):
(Unification hints seem to have no priority mechanism, and again relying on priorities for correctness, rather than speed, strikes me as somewhat dubious.)
Anne Baanen (Oct 10 2024 at 08:54):
Anne Baanen said:
Note that making docs#CommRingCat an
abbrev
doesn't fix the issue directly, since we still get an errorfailed to synthesize PreservesFilteredColimits (forget (Bundled CommRing))
.
I went back to this approach since it seems we really can't force Lean to stay with CommRingCat
. And indeed, we did the same change in #11595 for non-commutative ring categories. Turns out the trick to making this work is to also delete the instances giving a duplicate (concrete) category structure on the synonyms.
Anne Baanen (Oct 10 2024 at 08:55):
@Matthew Ballard Do you remember why #11595 only turns the defs into abbrevs for non-commutative rings?
Anne Baanen (Oct 10 2024 at 09:11):
Oh, I just noticed #15314 touches most of the files that we were struggling with, let's see if that fixes the issues!
Anne Baanen (Oct 10 2024 at 09:21):
It does not fix the issues :(
Anne Baanen (Oct 10 2024 at 09:37):
Anne Baanen said:
Anne Baanen said:
Note that making docs#CommRingCat an
abbrev
doesn't fix the issue directly, since we still get an errorfailed to synthesize PreservesFilteredColimits (forget (Bundled CommRing))
.I went back to this approach since it seems we really can't force Lean to stay with
CommRingCat
. And indeed, we did the same change in #11595 for non-commutative ring categories. Turns out the trick to making this work is to also delete the instances giving a duplicate (concrete) category structure on the synonyms.
This indeed seems to make everything build! Let's try splitting into an abbrev
PR and then a unif_hint
PR.
Anne Baanen (Oct 10 2024 at 10:02):
Done! #17612 turns CommRingCat
and CommSemiRingCat
into abbrev
s.
Matthew Ballard (Oct 10 2024 at 12:47):
Anne Baanen said:
Matthew Ballard Do you remember why #11595 only turns the defs into abbrevs for non-commutative rings?
I forgot commutative rings were a thing..? I just got annoyed by instRing
and instRing'
both existing
Anne Baanen (Oct 10 2024 at 14:44):
Matthew Ballard said:
I forgot commutative rings were a thing..? I just got annoyed by
instRing
andinstRing'
both existing
Interestingly for CommRingCat
both of the instances were still needed. (Because one is for the Bundled.\alpha
projection and the other is for the ConcreteCategory.hasCoeToSort
non-instance instance.)
Anne Baanen (Oct 10 2024 at 14:46):
I wrote a little macro rule for erw
that will first try rw
and complain if that succeeds:
import Mathlib.Tactic.TryThis
/-- if we call `erw`, first try running regular `rw` and warn if that succeds. -/
macro_rules
| `(tactic| erw $s $(loc)?) =>
`(tactic| try_this rw $(.none)? $s $(loc)?)
#17615 applies the output of this macro to the whole of Mathlib, getting rid of a lot of false positive erw
s!
Should this be included by default in Mathlib? There are some false positives where rw
succeeds but is not equivalent to erw
, see branch#erw-to-rw-setup.
Matthew Ballard (Oct 10 2024 at 15:24):
Anne Baanen said:
Should this be included by default in Mathlib? There are some false positives where
rw
succeeds but is not equivalent toerw
, see branch#erw-to-rw-setup.
I support this with some documentation for the end user to understand there can be different successful rewrites depending on the transparency.
Ruben Van de Velde (Oct 10 2024 at 15:27):
Something else that might be interesting: erw
chains where only one or a small number of rewrites need the e
Ruben Van de Velde (Oct 10 2024 at 15:27):
Probably harder than that macro, though
Anne Baanen (Oct 11 2024 at 08:41):
I played around with a few alternative implementations, but I think the simplicity of the current approach wins. So let's go with that, adding a thorough explanation of the subtleties: #17638
Anne Baanen (Oct 11 2024 at 14:38):
So I spent some more time today on trying to understand why we need so much erw
and I have some form of explanation.
Let's take ι_openEmbedding as an example. It starts by doing a rewrite, replacing the morphism (𝖣.ι i).base
with the composition of two morphisms. Note however that type of (𝖣.ι i).base : D.U i ⟶ D.glued
doesn't match the type of the composition: ((D.mapGlueData (forget C)).ι i ≫ (D.gluedIso (forget C)).inv) : (D.mapGlueData (forget C)).U i ⟶ (forget C).obj D.glued
. The two types are defeq but not reducibly so.
The problem is however that the goal is equal to OpenEmbedding ⇑(D.ι i).base
, where the coercion ⇑
has arguments D.U i
and D.glued
(and a few others). And rw
doesn't care about these extra arguments at first because they are not part of the subterm to rewrite. (AFAICT, rw
typechecks its motive at default transparency.) Then the next rw
call happens and it's looking for the types D.U i
and D.glued
in the morphism (based on the arguments to ⇑
). But rw
only sees (D.mapGlueData (forget C)).U i
and (forget C).obj D.glued
, notices they are not reducibly defeq, and gives up. erw
is able to unify the types and so it succeeds.
So that is the problem: we can rw
our goal into something that is not type correct at reducible level, so the next rw
errors on it.
Anne Baanen (Oct 11 2024 at 14:46):
A few solutions:
- Make more things reducible. I tried this and it works, but since there are so many operations on objects we'd just end up making this whole corner of the library reducible. (Idem for unification hints.)
- Fix the goal so it is type correct again. The problem is that some implicit arguments in the goal don't match the types we were expecting. So replace them with metavariables and reinfer them, correctly this time. Don't know how to put it into tactic code but
show
can do this for us if we copy and paste the displayed goal type. For example:
theorem ι_openEmbedding [HasLimits C] (i : D.J) : OpenEmbedding (𝖣.ι i).base := by
rw [← show _ = (𝖣.ι i).base from 𝖣.ι_gluedIso_inv (PresheafedSpace.forget _) _]
-- Reinfer the types:
show OpenEmbedding ((D.mapGlueData (forget C)).ι i ≫ (D.gluedIso (forget C)).inv)
rw [coe_comp] -- Works!
refine
OpenEmbedding.comp
(TopCat.homeoOfIso (𝖣.gluedIso (PresheafedSpace.forget _)).symm).openEmbedding
(D.toTopGlueData.ι_openEmbedding i)
- Don't use
rw
lemmas that are not reducibly type-correct. I assume this involves introducing lots of identity morphisms everywhere, and even if not it certainly requires a lot more lemmas than we have. - Think of a clever way to redefine docs#CategoryTheory.coe_comp. This seems to be a fruitful direction:
coe_comp
seems to be the most sensitive to these subtle non-defeq issues. So maybe we can rephrase it in such a way that it doesn't complain so often? Generalizing the thing just enough so it is still correct but no longer needs everything to be exactly equal? I played around with this but didn't find any easy ways to do it.
Anne Baanen (Oct 11 2024 at 14:47):
So that's pretty much where I'm stuck right now. I would appreciate any thoughts, especially insight from previous attempts.
Christian Merten (Nov 15 2024 at 09:22):
In #19065 I tried to implement a different approach to fixing this in AlgebraCat
, one of the less used algebraic, concrete categories (so it is less painful to do the experiment): Make things less def-eq. On current master, the category instance on AlgebraCat
is defined as
instance : Category (AlgebraCat.{v} R) where
Hom A B := A →ₐ[R] B
The suggestion is to make a one field structure
structure Hom (A B : AlgebraCat.{v} R) where
algHom : A →ₐ[R] B
and then define
instance : Category (AlgebraCat.{v} R) where
Hom A B := Hom A B
This gets rid of all erw
s and simp [...]; rfl
s in AlgebraCat.Basic
. I found the following example insightful: This is extracted from the current proof of docs#AlgebraCat.adj:
import Mathlib.Algebra.Category.AlgebraCat.Basic
open CategoryTheory AlgebraCat
variable {R : Type u} [CommRing R]
set_option pp.analyze true
example : free.{u} R ⊣ forget (AlgebraCat.{u} R) :=
Adjunction.mkOfHomEquiv
{ homEquiv := fun _ _ => (FreeAlgebra.lift _).symm
homEquiv_naturality_left_symm := by
intro X Y Z f g
apply FreeAlgebra.hom_ext
ext x
simp only [Equiv.symm_symm, Function.comp_apply, free_map]
/- Eq (α := Z)
(((FreeAlgebra.lift R) (f ≫ g) :
Prefunctor.obj (Functor.toPrefunctor (free R)) X ⟶ Z) (FreeAlgebra.ι R x) : ↑Z) _
-/
erw [FreeAlgebra.lift_ι_apply]
sorry
homEquiv_naturality_right := sorry }
While we can fix coe
lemmas by supplying no_index
or simply providing all possible combinations of .ofHom
and .of
, I think we can't fix this type of problem without making everything reducible. Namely here,
docs#FreeAlgebra.lift_ι_apply expects (FreeAlgebra.lift R) (f ≫ g)
to be of type FreeAlgebra R X →ₐ[R] Z
, but this is not reducibly def-eq to the synthesized type.
Eric Wieser (Nov 15 2024 at 10:07):
Christian Merten said:
The suggestion is to make a one field structure
structure Hom (A B : AlgebraCat.{v} R) where algHom : A →ₐ[R] B
and then define
instance : Category (AlgebraCat.{v} R) where Hom A B := A →ₐ[R] B
Did you mean
instance : Category (AlgebraCat.{v} R) where
Hom A B := Hom A B
Christian Merten (Nov 15 2024 at 10:52):
Oops, yes!
Jiang Jiedong (Nov 15 2024 at 17:08):
If applied to CommRingCat
, I believe this change will help in solving part of the IsLocalHom
issue.
This is a common phenomenon that due to defeq, when Lean need to infer the type F
in a theorem with signature {F} [FunLike F A B] (f : F)
, behaves undesirably when there are multiple defeq choices. This change will completely distinguish hom
from the real RingHom
.
Adam Topaz (Nov 15 2024 at 18:15):
I've been advocating for this approach for a while. It worked out quite well when I did a similar refactor for morphisms of sheaves, cf. CategoryTheory.Sheaf.Hom, even without the FunLike
infrastructure. It would be really nice if we also had some automation to help out here.
Andrew Yang (Nov 15 2024 at 21:18):
I’ve wrapped scheme morphisms into structures for this very same reason.
I did extends OldStructure
instead of one field structures which kept all the dot notations and it was quite nice.
Eric Wieser (Nov 15 2024 at 22:01):
I used this approach when building docs#QuadraticModuleCat.Hom too
Adam Topaz (Nov 16 2024 at 00:30):
For now, what do folks think about adding a simple command like this?
import Lean
open Lean Parser Elab Command
@[command_parser]
def wrapperParser := leading_parser
declModifiers false >> "wrap" >> declId >> (many <| Term.bracketedBinder) >> Term.typeSpec
@[command_elab wrapperParser]
def elabWrapper : CommandElab
| `($mods:declModifiers wrap $name $args* : $res) => do
elabCommand <| ← `($mods:declModifiers structure $name $args* where as : $res)
| _ => throwUnsupportedSyntax
@[ext]
wrap NatWrapper (a b : Nat) : Nat
#check NatWrapper.as
#check NatWrapper.mk
#check NatWrapper.ext
Adam Topaz (Nov 16 2024 at 00:32):
It doesn't to anything really, but it could be expanded later on.
Eric Wieser (Nov 16 2024 at 01:46):
I think this isn't really solving the right problem; I think we want something more like
@[wrap]
structure WrappedFoo where ofFoo :: toFoo : Foo
which gives the user the freedom to name things as they like, but then generates all the extra boilerplate around it
Eric Wieser (Nov 16 2024 at 01:47):
For category theory wrappers, we probably want to call the field hom
or similar, whereas elsewhere in mathlib we might want to choose different names
Adam Topaz (Nov 16 2024 at 01:55):
Yeah I think you’re right. Maybe we should collect some cases where wrapping in this way is useful. Category theory is an obvious one, especially for concrete categories. Any others?
Eric Wieser (Nov 16 2024 at 01:57):
Any type of "strong" type synonym like Polynomial
or SkewMonoidAlgebra
Eric Wieser (Nov 16 2024 at 01:58):
Arguably what's missing there is a deriving
handler for single-field structures.
Eric Wieser (Nov 16 2024 at 01:58):
Something that generates an Equiv
to the field would be handy too
Kim Morrison (Nov 18 2024 at 11:05):
I think we should get started on this! I think it's okay to work incrementally (i.e. we don't need to change all the concrete categories in one go)
Christian Merten (Nov 18 2024 at 11:12):
I have some progress for the ring categories in a branch that I can push, but there is a design question that I still have not figured out:
All the mentioned examples in this thread are examples without a FunLike
instance on the homs and a lot of the coe_comp
issues with the current design have no reason to vanish with the one-field structure design. Consider for example:
Toy
As you can see, we still need 9 coe_
lemmas to make all the examples work. Can we do better?
Christian Merten (Nov 18 2024 at 11:14):
A good sign is that so far I have not needed more no_index
tweaking.
Christian Merten (Nov 18 2024 at 11:17):
I started a try on automatically performing theses tests, but my meta skills are too non-existent to make significant progress.
Eric Wieser (Nov 18 2024 at 11:27):
Do we actually want a FunLike (X ⟶ Y) X Y
instance?
Eric Wieser (Nov 18 2024 at 11:28):
I'd argue that if you want to talk about applying functions, you should jump out of category theory land and write f.hom
instead
Christian Merten (Nov 18 2024 at 11:30):
I had the same thought a few days ago, but I think we want this instance. For example AG is already now very heavy on notation, so without the FunLike
instance for a morphism of schemes f
, application to an element would now be f.base.hom x
. Maybe this could be remedied by a shortcut Scheme.Hom.apply
, but this would then lead to a bunch of new _apply
lemmas.
Christian Merten (Nov 18 2024 at 11:32):
(Note that with the current approach, all these coe
lemmas are also needed and you can convince yourself that taking the test examples from above and specializing to e.g. CommRingCat
many of them will fail.)
Johan Commelin (Nov 18 2024 at 11:34):
Christian Merten said:
I had the same thought a few days ago, but I think we want this instance. For example AG is already now very heavy on notation, so without the
FunLike
instance for a morphism of schemesf
, application to an element would now bef.base.hom x
. Maybe this could be remedied by a shortcutScheme.Hom.apply
, but this would then lead to a bunch of new_apply
lemmas.
So maybe we should create shortcut abbrev
s specifically for schemes. Because they are a pretty special case within the larger zoo of concrete categories. (In particular they are not concrete...)
Christian Merten (Nov 18 2024 at 11:34):
I don't really see a problem with adding (many) boilerplate coe
lemmas, but we have to do it systematically. An automatic way of generating the tests above would be a first step in this direction.
Eric Wieser (Nov 18 2024 at 11:36):
Avoiding FunLike
seems to solve the problem above
Eric Wieser (Nov 18 2024 at 11:37):
Christian Merten (Nov 18 2024 at 11:37):
Johan Commelin said:
Christian Merten said:
I had the same thought a few days ago, but I think we want this instance. For example AG is already now very heavy on notation, so without the
FunLike
instance for a morphism of schemesf
, application to an element would now bef.base.hom x
. Maybe this could be remedied by a shortcutScheme.Hom.apply
, but this would then lead to a bunch of new_apply
lemmas.So maybe we should create shortcut
abbrev
s specifically for schemes. Because they are a pretty special case within the larger zoo of concrete categories. (In particular they are not concrete...)
And (locally) ringed spaces, adic spaces, etc. I think adding the coe
lemmas for TopCat
in this case is less boilerplat than adding them for every cat. building on top of that.
Christian Merten (Nov 18 2024 at 11:38):
Aha, CoeFun
it is then?
Johan Commelin (Nov 18 2024 at 11:40):
But then none of the map_add
and map_mul
lemmas will be able to fire, right?
Christian Merten (Nov 18 2024 at 11:40):
Ah, but we can't have FooHomClass (M ⟶ N) ...
then, right?
Christian Merten (Nov 18 2024 at 11:43):
@Eric Wieser you say that CoeFun
is inlined, unlike FunLike
. What does this mean? Could FunLike
be also inlined?
Anne Baanen (Nov 18 2024 at 11:44):
Christian Merten said:
Eric Wieser you say that
CoeFun
is inlined, unlikeFunLike
. What does this mean? CouldFunLike
be also inlined?
This means when we write f x
we don't see CoeFun.coe f x
, but f.toFun x
. With a FunLike
instance we'd get FunLike.toFun f x
.
Anne Baanen (Nov 18 2024 at 11:45):
FooHomClass
es need the FunLike.toFun
there, otherwise map_foo f : FunLike.toFun f foo = foo
wouldn't fire on Hom.toFun f foo
.
Eric Wieser (Nov 18 2024 at 11:47):
In reality isn't SomeCategoryHom.toFun
a type like AddMonoidHom
anyway?
Christian Merten (Nov 18 2024 at 11:47):
But if f x
becomes f.toFun x
why does the FooHomClass
instance for f.toFun
not trigger?
Eric Wieser (Nov 18 2024 at 11:47):
So writing some_hom.toFun x
really means DFunLike.coe some_hom.toFun x
Eric Wieser (Nov 18 2024 at 11:48):
Christian Merten said:
But if
f x
becomesf.toFun x
why does theFooHomClass
instance forf.toFun
not trigger?
Do you have an example of this happening?
Christian Merten (Nov 18 2024 at 11:48):
Eric Wieser said:
Christian Merten said:
But if
f x
becomesf.toFun x
why does theFooHomClass
instance forf.toFun
not trigger?Do you have an example of this happening?
I deduced this from Johan's message above, let me try.
Eric Wieser (Nov 18 2024 at 11:50):
I think you confused things by calling the field toFun
instead of toHom
or hom
or deCat
or whatever
Johan Commelin (Nov 18 2024 at 11:50):
Yeah, I probably got confused there
Christian Merten (Nov 18 2024 at 11:51):
Indeed, I confirm when applying Eric's suggestion above to #19065, the following tests all work:
example {M N : AlgebraCat.{v} R} (f : M ⟶ N) (x y : M) : f (x + y) = f x + f y := by
simp
example {M N : AlgebraCat.{v} R} (f : M ⟶ N) : f 0 = 0 := by
simp
example {M N : AlgebraCat.{v} R} (f : M ⟶ N) (r : R) (m : M) : f (r • m) = r • f m := by
simp
Eric Wieser (Nov 18 2024 at 11:52):
But my (perhaps-uninformed) recommendation would still be to not have the CoeFun
instance at all
Eric Wieser (Nov 18 2024 at 11:53):
It's harmless enough, but now you have to decide whether to call the lemmas toFun_comp
or coe_comp
Christian Merten (Nov 18 2024 at 11:53):
But isn't the CoeFun
instance precisely doing what you suggested, but hidden by nice notation? I.e. f x
is unfolded to f.hom x
anyway, so I see no problem.
Eric Wieser (Nov 18 2024 at 11:53):
How often do you actually write f x
when working in, say, AlgebraCat
?
Eric Wieser (Nov 18 2024 at 11:54):
Isn't the point of category theory that you don't really "apply" your morphisms in the first place, and treat them as abstract arrows?
Christian Merten (Nov 18 2024 at 11:54):
I do write f x
very often when working with CommRingCat
or TopCat
in the context of algebraic geometry.
Christian Merten (Nov 18 2024 at 11:55):
Eric Wieser said:
Isn't the point of category theory that you don't really "apply" your morphisms in the first place, and treat them as abstract arrows?
With concrete categories, you certainly want and need both perspectives.
Christian Merten (Nov 18 2024 at 14:45):
I updated #19065 with the CoeFun
suggestion and added a bunch of example
tests verifying that indeed simp
works nicely.
Unfortunately, the benchmark does not look very good. There seems to be a +1.21% on lint
which sounds like a big change and also some slowdown in Mathlib.Algebra.Cateogry.AlgebraCat.Limits
, which I don't understand. Does anyone have an idea what is going wrong here?
Christian Merten (Nov 18 2024 at 14:46):
(the +1.21% was already there before adapting the CoeFun
change)
Anne Baanen (Nov 18 2024 at 16:08):
I never really understood why the one-member structure helps until I see the effects in real life, and now I think I get it. By giving the algHom
projection its own name, we avoid identifying ⟶
and →ₐ
. So then the instances on those types don't get mixed up. That seems to be the main source of erw
s: when one rewrite saw through the defeq of ⟶
and →ₐ
but then another one couldn't. Thanks for putting it in practice :light_bulb:
Christian Merten (Nov 18 2024 at 16:10):
And I am confident that with the CoeFun
approach and hence no FooHomClass
, this will also get rid of all the struggles with things like docs#IsLocalHom because there can't be confusion in the F
variable anymore.
Anne Baanen (Nov 18 2024 at 16:11):
Right, because there is no FooHomClass
on the Hom
structure, but there is still one on the projection via CoeFun
, it actually still works. Very clever!
Kevin Buzzard (Nov 18 2024 at 16:58):
Write a community blog post while the ideas are still fresh in your mind @Christian Merten !
Eric Wieser (Nov 18 2024 at 17:16):
Anne Baanen said:
By giving the
algHom
projection its own name, we avoid identifying⟶
and→ₐ
It also means we can actually state lemmas like "categorical composition is LinearMap.comp` etc (as in docs#QuadraticModuleCat.toIsometry_comp) without this weird identification being used to make the types of the LHS and RHS match (as in docs#ModuleCat.comp_def)
Eric Wieser (Nov 18 2024 at 17:17):
Anne Baanen said:
because there is no
FooHomClass
on theHom
structure, but there is still one on the projection viaCoeFun
, it actually still works
note that in the infoview it will presumably still display as f.hom x
, but I guess being able to write f x
is the main thing
Eric Wieser (Nov 18 2024 at 17:17):
(and of course a custom delaborator for categorical morphisms is totally in the space of reasonable things to do)
Matthew Ballard (Nov 18 2024 at 17:32):
I left a comment on the PR to avoid some eta expansions. Like Eric I find the CoeFun
’s somewhat icky but the battle is already lost with the CoeSort
I think.
Should forget
be inlined in addition to being reducible
?
Eric Wieser (Nov 18 2024 at 17:33):
Agreed that the CoeFun
and CoeSort
are at least consistent
Eric Wieser (Nov 18 2024 at 17:35):
In the longer run I suppose you could do something nuts like ⧘f⧙ x
and f : ⧘X⧙ →ₐ ⧘Y⧙
to explicitly mark the boundaries between categorical and non-categorical objects, with less visual noise than .toFun
, .hom
, etc
Matthew Ballard (Nov 18 2024 at 17:40):
Yes, anonymous destructors would be nice
Eric Wieser (Nov 18 2024 at 17:48):
Oh, I was just proposing a scoped notation specific to CategoryTheory
Edward van de Meent (Nov 18 2024 at 19:53):
this can just be a notation class, right?
Edward van de Meent (Nov 18 2024 at 19:55):
now of course ⧘
and ⧙
are nontrivial to type, so that will need some adaptation too, but this does seem like a workable solution.
Christian Merten (Nov 18 2024 at 20:10):
The main performance penalty of scrapping FunLike
seems to come from instance synthesization of MulHomClass
etc. for the underlying AlgHom
. When using (the sadly deprecated) AlgHom.map_{one,zero,mul,add}
instead of the corresponding map_{one,zero,mul,add}
we are back to the old performance with the FunLike
instance. This is a problem I have already encountered many times with AlgHom
s, that the corresponding MulHomClass
and friends take long to synthesize (sometimes leading to timeouts). So I think this is unrelated to this PR, it's just surfacing now, because f x
gets unfolded to f.algHom x
now. Should we accept this in this change then and investigate the synthesization slowness independently of this PR?
Yaël Dillies (Nov 18 2024 at 20:11):
Christian Merten said:
When using (the sadly deprecated)
AlgHom.map_{one,zero,mul,add}
instead of the correspondingmap_{one,zero,mul,add}
we are back to the old performance with theFunLike
instance
Can we simply undeprecate those lemmas?
Yaël Dillies (Nov 18 2024 at 20:11):
cc @Kim Morrison who is currently removing uses of it throughout mathlib
Christian Merten (Nov 18 2024 at 20:12):
Yaël Dillies said:
Christian Merten said:
When using (the sadly deprecated)
AlgHom.map_{one,zero,mul,add}
instead of the correspondingmap_{one,zero,mul,add}
we are back to the old performance with theFunLike
instanceCan we simply undeprecate those lemmas?
I would be in favor of this.
Matthew Ballard (Nov 18 2024 at 20:25):
It looks like it was originally done in #14168
Matthew Ballard (Nov 18 2024 at 20:31):
The keys for docs#map_pow
@DFunLike.coe _ _ _ _ _ (@HPow.hPow _ ℕ _ _ _ _)
@DFunLike.coe (AlgHom _ _ _ _ _ _ _ _) _ _ _ _ (@HPow.hPow _ ℕ _ _ _ _)
Matthew Ballard (Nov 18 2024 at 20:34):
The priority of docs#map_pow has already been lowered for this reason
Kevin Buzzard (Nov 18 2024 at 20:51):
We could also do with a blog post on what the heck keys/discrimination trees are and what a mathematician needs to know about them.
In 2017/18 sometimes when I understood something useful I'd write a Xena blog post and some of these still get referred to in Zulip now
Ruben Van de Velde (Nov 18 2024 at 21:14):
Yeah, I thought that was me. Duplicating all those lemmas across all the kinds of homs that they apply to is pretty bad - I'd hope we'd only do that as a temporary measure while we wait for lean to get cleverer
Eric Wieser (Nov 18 2024 at 21:24):
Matthew Ballard said:
The keys for docs#map_pow
@DFunLike.coe _ _ _ _ _ (@HPow.hPow _ ℕ _ _ _ _)
@DFunLike.coe (AlgHom _ _ _ _ _ _ _ _) _ _ _ _ (@HPow.hPow _ ℕ _ _ _ _)
This looks to be working as intended to me, so long as the subsequent MonoidHomClass
lookup that happens after trying the key is fast
Christian Merten (Nov 18 2024 at 23:03):
I found the two bad simp
lemmas in #19065: AlgebraCat.forget_obj
and AlgebraCat.forget_map
, they have the keys:
#[Prefunctor.0, *, CategoryTheory.Functor.0, CategoryTheory.ConcreteCategory.0, *]
and
#[Prefunctor.1, *, *, *, CategoryTheory.Functor.0, CategoryTheory.ConcreteCategory.0, *]
respectively. So neither of them mention AlgebraCat
. Is this expected?
In the meantime, I just removed the @[simp]
from these (they are not @[simp]
on master either) and the +1.2% lint is gone.
Christian Merten (Nov 18 2024 at 23:54):
@Eric Wieser suggested to do a poll on the naming of the field of AlgebraCat.Hom
. For comparison, docs#QuadraticModuleCat uses toIsometry
, docs#AlgebraicGeometry.Scheme uses toHom
with a custom simps projection toLRSHom
. So here goes a poll:
Christian Merten (Nov 18 2024 at 23:55):
/poll How should we name the field in FooCat.Hom
?
toFooHom
(e.g. toRingHom
, toAlgHom
)
fooHom
(e.g. ringHom
, algHom
)
toHom
hom
Eric Wieser (Nov 18 2024 at 23:58):
One downside of hom
is that when working with e : X ≅ Y
, the underlying map is e.hom.hom
Christian Merten (Nov 18 2024 at 23:59):
True, but also it is the shortest to type.
Christian Merten (Nov 19 2024 at 00:00):
(but slightly strange in the case Foo = Top
)
Christian Merten (Nov 19 2024 at 09:46):
The poll has a tendency towards .hom
so I went with this.
Johan Commelin (Nov 19 2024 at 14:29):
I could even imagine refactoring Iso
to have a .fwd
and .inv
instead of .hom
and .inv
.
Anne Baanen (Nov 25 2024 at 13:22):
The AlgebraCat
hom structure got merged! I'm taking a look at ModuleCat
now...
Anne Baanen (Nov 26 2024 at 14:22):
I pushed my progress to PR #19511. I got it almost working, except for two files:
In file#Mathlib/Algebra/Category/ModuleCat/Monoidal/Closed there are two definitions sorry
'd there that should be easy to implement for someone who knows what they're doing.
In file#Mathlib/AlgebraicGeometry/Modules/Tilde we call docs#Limits.Concrete.colimit_no_zero_smul_divisor which essentially asserts we're in a subcategory of ModuleCat
by requiring a LinearMapClass
instance on the homs. But the point of doing this refactor is to make the homs not a linear map, so I'm not sure how to proceed. There are also a few errors downstream of that that I haven't looked at in details.
Christian Merten (Nov 26 2024 at 14:26):
Can we reformulate docs#colimit_no_zero_smul_divisor with the assumption [HasForget₂ C ModuleCat]
?
Anne Baanen (Nov 26 2024 at 14:35):
Ooh, that's a good idea! I have to work on other projects right now, feel free to make any changes you want to the PR :)
Anne Baanen (Nov 26 2024 at 14:35):
I left a lot of comments on the PR with todo notes.
Anne Baanen (Nov 28 2024 at 14:03):
I had some time today to try out the HasForget₂
suggestion, and unfortunately I don't understand this enough to make substantial progress.
I feel like something like the below should work, but I get type mismatches and can't find how to deal with them:
import Mathlib.CategoryTheory.Limits.ConcreteCategory.Basic
import Mathlib.Algebra.Category.ModuleCat.Basic
import Mathlib.Tactic.CategoryTheory.Elementwise
universe t w v u r
open CategoryTheory
namespace CategoryTheory.Limits.Concrete
attribute [local instance] ConcreteCategory.instFunLike ConcreteCategory.hasCoeToSort
variable {C : Type u} [Category.{v} C] [ConcreteCategory.{w} C]
/--
If the forgetful functors factors through `ModuleCat`, morphisms preserve zero.
-/
-- TODO: use something like `HasForget₂ C AddMonCat` instead?
lemma map_zero_of_hasForget₂ (R : Type*) [Ring R] [∀ c : C, AddCommMonoid c] [∀ c : C, Module R c]
[HasForget₂ C (ModuleCat R)] {X Y : C} (f : X ⟶ Y) :
f 0 = 0 := by
convert ((forget₂ C (ModuleCat R)).map f).hom.map_zero
-- goal contains `((forget₂ C (ModuleCat R)).map f).hom` of type
-- `(forget₂ C (ModuleCat R)).obj X →ₗ[R] (forget₂ C (ModuleCat R)).obj Y`
-- we want `f : (forget C).obj X → (forget C).obj Y`
sorry
/--
If the forgetful functors factors through `ModuleCat`, morphisms preserve scalar multiplication.
-/
lemma map_smul_of_hasForget₂ {R : Type*} [Ring R] [∀ c : C, AddCommMonoid c] [∀ c : C, Module R c]
[HasForget₂ C (ModuleCat R)] (r : R) {X Y : C} (f : X ⟶ Y) (x : (forget C).obj X) :
f (r • x) = r • f x := by
convert ((forget₂ C (ModuleCat R)).map f).hom.map_smul r _
· -- goal contains `((forget₂ C (ModuleCat R)).map f).hom` of type
-- `(forget₂ C (ModuleCat R)).obj X →ₗ[R] (forget₂ C (ModuleCat R)).obj Y`
-- we want `f : (forget C).obj X → (forget C).obj Y`
sorry
· -- `x : (forget C).obj X`
-- we want a `(forget₂ C (ModuleCat R)).obj X`
sorry
Anne Baanen (Nov 28 2024 at 14:04):
Anyone who actually knows what they're doing who can advise? :)
Christian Merten (Nov 28 2024 at 14:06):
I think these instances [∀ c : C, AddCommMonoid c]
cause trouble here. The HasForget_2
will give a different AddCommMonoid
instance.
Christian Merten (Nov 28 2024 at 14:09):
The issue seems to me that the only sensible spelling of your first lemma is
lemma map_zero_of_hasForget₂ (R : Type*) [Ring R]
[HasForget₂ C (ModuleCat R)] {X Y : C} (f : X ⟶ Y) :
(forget₂ C (ModuleCat R)).map f 0 = 0 := by
simp
which is of course not very insightful.
Anne Baanen (Nov 28 2024 at 14:10):
Christian Merten said:
I think these instances
[∀ c : C, AddCommMonoid c]
cause trouble here. TheHasForget_2
will give a differentAddCommMonoid
instance.
Hmm, but if I remove those instances it says the HSMul R ((forget C).obj X) ?m.4059
instance can't be found...
Anne Baanen (Nov 28 2024 at 14:12):
I suppose an alternative solution is to add the LinearMapClass
instances locally in the file defining docs#Limits.Concrete.colimit_no_zero_smul_divisor ?
Anne Baanen (Nov 28 2024 at 14:12):
That is: HasForget_2 C (ModuleCat R)
implies LinearMapClass (X --> Y)
Christian Merten (Nov 28 2024 at 14:13):
Maybe for now it is fine to just formulate the problematic file for ModuleCat
? Or are they used in cases where C
is not ModuleCat
? I think this should not stop this refactor.
Anne Baanen (Nov 28 2024 at 14:14):
It seems to be only used for ModuleCat
itself: rg
reports
Mathlib/AlgebraicGeometry/Modules/Tilde.lean
192: refine Limits.Concrete.colimit_no_zero_smul_divisor
Mathlib/CategoryTheory/Limits/ConcreteCategory/WithAlgebraicStructures.lean
22:- `CategoryTheory.Limits.Concrete.colimit_no_zero_smul_divisor`: Let `C` be a category where its
59:lemma colimit_no_zero_smul_divisor
Christian Merten (Nov 28 2024 at 14:15):
Anne Baanen said:
That is:
HasForget_2 C (ModuleCat R)
impliesLinearMapClass (X --> Y)
This won't work until you also have instances like
instance [HasForget₂ C AddMonCat] (X : C) : AddCommMonoid X :=
sorry
right?
Anne Baanen (Nov 28 2024 at 14:16):
Christian Merten said:
Anne Baanen said:
That is:
HasForget_2 C (ModuleCat R)
impliesLinearMapClass (X --> Y)
This won't work until you also have instances like
instance [HasForget₂ C AddMonCat] (X : C) : AddCommMonoid X := sorry
right?
Ah, because otherwise there is nothing saying the AddCommMonoid X
instance coincides with the one induced by the forget_2
map?
Christian Merten (Nov 28 2024 at 14:16):
Yes
Christian Merten (Nov 28 2024 at 14:18):
And I don't think there is a good way to get this AddCommMonoid X
instance in a sane way with the current definition of HasForget_2
. Maybe HasForget_2
should be changed to:
class HasForget₂ (C : Type u) (D : Type u') [Category.{v} C] [ConcreteCategory.{w} C]
[Category.{v'} D] [ConcreteCategory.{w} D] where
forget₂ : C ⥤ D
forgetComp : forget₂ ⋙ forget D ≅ forget C
So that we can have data carrying arround the actual isomorphism forget₂ ⋙ forget D ≅ forget C
. Then we should be able to have instances like
instance [HasForget₂ C AddMonCat] (X : C) : AddCommMonoid X :=
sorry
by transporting the AddCommMonoid
instance on (forget AddMonCat).obj ((forget₂ C AddMonCat).obj X)
via the isomorphism forgetComp.app X
to (forget C).obj X
.
Christian Merten (Nov 28 2024 at 14:29):
But this has nothing to do with the ModuleCat
refactor, so I vote for just specializing the problematic file to ModuleCat
for now.
Anne Baanen (Nov 29 2024 at 12:42):
I did a lot of cleanup in #19511 and it should now be ready for review :D
Johan Commelin (Nov 29 2024 at 23:40):
Major effort! Everybody who regularly works with these types of categories is invited to take a look.
cc @Christian Merten @Andrew Yang @Junyan Xu (random subset, others are welcome to review as well).
Kim Morrison (Dec 03 2024 at 06:18):
Mostly this is looking really nice.
But there are some problems where we need to fill in too many sources and targets by hand. e.g. Mathlib.AlgebraicGeometry.Modules.Tilde
. So further pinging @Jujian Zhang to see if they have suggestions!
Johan Commelin (Dec 04 2024 at 16:18):
I agree that the diff is looking very nice now.
@Joël Riou do you have opinions on this refactor?
Joël Riou (Dec 04 2024 at 18:07):
This is a great effort, and it certainly goes in the right direction!
Anne Baanen (Dec 05 2024 at 14:30):
I have been experimenting with making a class for ConcreteCategory
which has a bundled Hom
type, so that we can write elementwise lemmas in high generality without having to convert them from ConcreteCategory.forget
-style definitions. (This was prompted by Kim's comment on https://github.com/leanprover-community/mathlib4/pull/19511#discussion_r1867083077).
import Mathlib
namespace CategoryTheory
universe w v u
class BundledHomCategory (C : Type u) [Category.{v} C]
(F : outParam <| C → C → Type*) (carrier : outParam <| C → Type w)
[∀ X Y, FunLike (F X Y) (carrier X) (carrier Y)] where
(hom : ∀ {X Y}, (X ⟶ Y) → F X Y)
(ofHom : ∀ {X Y}, F X Y → (X ⟶ Y))
(hom_ofHom : ∀ {X Y} (f : F X Y), hom (ofHom f) = f := by aesop_cat)
(ofHom_hom : ∀ {X Y} (f : X ⟶ Y), ofHom (hom f) = f := by aesop_cat)
(hom_id_apply : ∀ {X} (x : carrier X), hom (𝟙 X) x = x := by aesop_cat)
(hom_comp_apply : ∀ {X Y Z} (f : X ⟶ Y) (g : Y ⟶ Z) (x : carrier X),
hom (f ≫ g) x = hom g (hom f x) := by aesop_cat)
class BundledZeroHomCategory (C : Type u) [Category.{v} C]
(F : outParam <| C → C → Type*) (carrier : outParam <| C → Type w)
[∀ X Y, FunLike (F X Y) (carrier X) (carrier Y)]
[CategoryTheory.Limits.HasZeroMorphisms C] [∀ X, Zero (carrier X)]
extends BundledHomCategory C F carrier where
(hom_zero_apply : ∀ {X Y} (x : carrier X), hom (0 : X ⟶ Y) x = 0 := by aesop_cat)
namespace BundledHomCategory
variable {C : Type u} [Category.{v} C] {F : C → C → Type*} {carrier : C → Type w}
variable [∀ X Y, FunLike (F X Y) (carrier X) (carrier Y)]
variable [BundledHomCategory C F carrier]
attribute [simp] hom_id_apply hom_comp_apply
instance {X Y : C} : CoeFun (X ⟶ Y) (fun _ ↦ carrier X → carrier Y) where
coe f := hom f
def homEquiv {X Y : C} : (X ⟶ Y) ≃ F X Y where
toFun := hom
invFun := ofHom
left_inv := ofHom_hom
right_inv := hom_ofHom
lemma hom_bijective {X Y : C} : Function.Bijective (hom : (X ⟶ Y) → F X Y) :=
homEquiv.bijective
lemma hom_injective {X Y : C} : Function.Injective (hom : (X ⟶ Y) → F X Y) :=
hom_bijective.injective
@[ext] lemma ext {X Y : C} {f g : X ⟶ Y} (h : hom f = hom g) : f = g :=
hom_injective h
@[ext]
lemma coe_ext {X Y : C} {f g : X ⟶ Y} (h : ⇑(hom f) = ⇑(hom g)) : f = g :=
ext (DFunLike.coe_injective h)
@[ext]
lemma ext_apply {X Y : C} {f g : X ⟶ Y} (h : ∀ x, f x = g x) : f = g :=
ext (DFunLike.ext _ _ h)
instance : ConcreteCategory C where
forget.obj := carrier
forget.map f := ⇑(hom f)
forget_faithful.map_injective h := coe_ext h
end BundledHomCategory
namespace BundledZeroHomCategory
open BundledHomCategory
variable {C : Type u} [Category.{v} C] {F : C → C → Type*} {carrier : C → Type w}
variable [∀ X Y, FunLike (F X Y) (carrier X) (carrier Y)]
variable [CategoryTheory.Limits.HasZeroMorphisms C] [∀ X, Zero (carrier X)]
variable [BundledZeroHomCategory C F carrier]
attribute [simp] hom_zero_apply
open Limits
@[simp]
lemma cokernel_condition
{X Y : C} (f : X ⟶ Y) [CategoryTheory.Limits.HasCokernel f]
(x) :
hom (cokernel.π f) (hom f x) = 0 := by
simpa only [hom_comp_apply, hom_zero_apply x]
using BundledHomCategory.ext_apply_iff.mp (CategoryTheory.Limits.cokernel.condition f) x
#discr_tree_key cokernel_condition
end BundledZeroHomCategory
namespace ModuleCat
open Limits BundledHomCategory BundledZeroHomCategory
variable {R : Type u} [Ring R]
instance : BundledZeroHomCategory (ModuleCat.{v} R) (fun M N ↦ M →ₗ[R] N) (ModuleCat.carrier) where
hom := ModuleCat.Hom.hom
ofHom := ModuleCat.ofHom
lemma foo {M N : ModuleCat.{v} R} (g : M ⟶ N) (x : M) : hom (cokernel.π g) (hom g x) = 0 := by simp
#discr_tree_key foo
end ModuleCat
end CategoryTheory
/-! ## Can we restate the specialized lemmas about colimits in general again? -/
universe t w v u r
open CategoryTheory
namespace CategoryTheory.Limits.Concrete
variable {J : Type w} [Category.{r} J]
variable {C : Type u} [Category.{v} C] (carrier : C → Type (max t w))
variable {FC : C → C → Type*} [∀ X Y, FunLike (FC X Y) (carrier X) (carrier Y)]
variable [BundledHomCategory C FC carrier]
section zero
open BundledHomCategory
lemma colimit_rep_eq_iff_exists' (F : J ⥤ C) [PreservesColimit F (forget C)] [IsFiltered J]
[HasColimit F] {i j : J} (x : (forget C).obj (F.obj i)) (y : (forget C).obj (F.obj j)) :
(colimit.ι F i) x = (colimit.ι F j) y ↔ ∃ (k : _) (f : i ⟶ k) (g : j ⟶ k), F.map f x = F.map g y :=
colimit_rep_eq_iff_exists F x y
theorem colimit_rep_eq_zero'
(F : J ⥤ C) [PreservesColimit F (forget C)] [IsFiltered J]
[∀ c : C, Zero ((forget C).obj c)]
[∀ {c c' : C}, ZeroHomClass (FC c c') ((forget C).obj c) ((forget C).obj c')] [HasColimit F]
(j : J) (x : (forget C).obj (F.obj j)) (hx : colimit.ι F j x = 0) :
∃ (j' : J) (i : j ⟶ j'), F.map i x = 0 := by
rw [show 0 = colimit.ι F j 0 by simp, colimit_rep_eq_iff_exists'] at hx
obtain ⟨j', i, y, g⟩ := hx
exact ⟨j', i, g ▸ by simp⟩
end zero
section module
open BundledHomCategory
theorem colimit_exists_rep' (F : J ⥤ C) [PreservesColimit F (forget C)]
[HasColimit F] (x : carrier (colimit F)) :
∃ (j : J) (y : carrier (F.obj j)), colimit.ι F j y = x :=
colimit_exists_rep F x
/--
if `r` has no zero smul divisors for all small-enough sections, then `r` has no zero smul divisors
in the colimit.
-/
@[simp]
lemma colimit_no_zero_smul_divisor'
(F : J ⥤ C) [PreservesColimit F (forget C)] [IsFiltered J] [HasColimit F]
(R : Type*) [Semiring R]
[∀ c : C, AddCommMonoid (carrier c)] [∀ c : C, Module R (carrier c)]
[∀ {c c' : C}, LinearMapClass (FC c c') R (carrier c) (carrier c')]
(r : R) (H : ∃ (j' : J), ∀ (j : J) (_ : j' ⟶ j), ∀ (c : carrier (F.obj j)), r • c = 0 → c = 0)
(x : (forget C).obj (colimit F)) (hx : r • x = 0) : x = 0 := by
classical
obtain ⟨j, x, rfl⟩ := Concrete.colimit_exists_rep' carrier F x
rw [← map_smul (BundledHomCategory.hom (colimit.ι F j))] at hx
obtain ⟨j', i, h⟩ := Concrete.colimit_rep_eq_zero' (hx := hx)
obtain ⟨j'', H⟩ := H
-- Restate to apply to `hom` instead of `ConcreteCategory.instFunLike`.
have : ∀ {j j'} (i : j ⟶ j') (x : carrier (F.obj j)), (colimit.ι F j') ((F.map i) x) = (colimit.ι F j) x :=
fun i => elementwise_of% (colimit.w F i)
simpa [this, map_zero] using congr(colimit.ι F _
$(H (IsFiltered.sup {j, j', j''} { ⟨j, j', by simp, by simp, i⟩ })
(IsFiltered.toSup _ _ <| by simp)
(F.map (IsFiltered.toSup _ _ <| by simp) x)
(by rw [← IsFiltered.toSup_commutes (f := i) (mY := by simp) (mf := by simp), F.map_comp,
hom_comp_apply, ← map_smul, ← map_smul, h, map_zero])))
end module
end CategoryTheory.Limits.Concrete
/-! ## Can we apply the above for `ModuleCat`? -/
namespace ModuleCat
open CategoryTheory CategoryTheory.Limits BundledHomCategory
variable {R : Type*} [Ring R]
theorem cokernel_π_imageSubobject_ext' {L M N : ModuleCat.{v} R} (f : L ⟶ M) [HasImage f]
(g : (imageSubobject f : ModuleCat.{v} R) ⟶ N) [HasCokernel g] {x y : N} (l : L)
(w : x = y + hom g (factorThruImageSubobject f l)) : hom (cokernel.π g) x = hom (cokernel.π g) y := by
subst w
simp
end ModuleCat
Anne Baanen (Dec 05 2024 at 14:34):
The main drawback I see here is an extra level of indirection on the hom
field of the class, so hom (ofHom f)
isn't reducibly defeq to f
anymore:
-- Current setup with #19511 reduces.
example (M N : ModuleCat.{v} R) (f : M →ₗ[R] N) :
ModuleCat.Hom.hom (ofHom f : M ⟶ N) = f := by
with_reducible rfl
-- Indirection via a typeclass doesn't reduce.
example (M N : ModuleCat.{v} R) (f : M →ₗ[R] N) :
BundledHomCategory.hom (BundledHomCategory.ofHom f : M ⟶ N) = f := by
fail_if_success with_reducible rfl
with_reducible_and_instances rfl -- We need one step higher on the reducibility ladder.
Anne Baanen (Dec 05 2024 at 14:35):
There's also the verbose boilerplates of the variable
lines:
variable {C : Type u} [Category.{v} C] (carrier : C → Type (max t w))
variable {FC : C → C → Type*} [∀ X Y, FunLike (FC X Y) (carrier X) (carrier Y)]
variable [BundledHomCategory C FC carrier]
But this is only necessary when working in generality over concrete categories, and hopefully we can upgrade the elementwise
metaprograms for generating most of that from abstract categorical statements.
Anne Baanen (Dec 05 2024 at 14:37):
I'm happily surprised that deriving ConcreteCategory
from BundledHomCategory
doesn't cause any interop issues with existing bits of the library, like for colimit_rep_eq_iff_exists'
, colimit_rep_eq_zero'
and colimit_no_zero_smul_divisor'
.
Kim Morrison (Dec 05 2024 at 22:16):
I'm in favour!
Kim Morrison (Dec 05 2024 at 22:17):
I wonder if we should push the AlgebraCat/ModuleCat refactor a bit further first, though.
Christian Merten (Dec 05 2024 at 22:17):
CommRingCat
and co. are in the works ...
Anne Baanen (Dec 05 2024 at 22:18):
Indeed, I might well be missing some issue that lurks in a very different category (topological spaces?)
Anne Baanen (Dec 05 2024 at 22:19):
On the other hand, the more we continue on the existing path, the more .hom
s we'll need to turn into hom _
s. :)
Kim Morrison (Dec 05 2024 at 22:20):
CommRingCat is definitely the next most important example. If we want "coverage", then TopCommRingCat is good for having many typeclasses.
Christian Merten (Dec 05 2024 at 23:58):
With CommRingCat
I am now running into two major obstacles:
- The first one I think is caused by the existence of
ConcreteCategory.instFunLike
, see https://github.com/leanprover-community/mathlib4/pull/19757/files#r1872319971. I think to work around that we would need lemmas like
lemma CommRingCat.forget_map_apply {R S : CommRingCat} (f : R ⟶ S)
(x : (CategoryTheory.forget CommRingCat).obj R) :
@DFunLike.coe _ _ _ ConcreteCategory.instFunLike f x = f x :=
rfl
to go back and forth between DFunLike.coe f x
and f.hom x
. But this is painful. Instead I think it is worth a try to replace ConcreteCategory.instFunLike
by a CoeFun
.
- The second one is similar to the one @Anne Baanen experienced with
ModuleCat
: In file#Mathlib/Topology/Sheaves/CommRingCat we have the following:
/-- A subpresheaf with a submonoid structure on each of the components. -/
structure SubmonoidPresheaf [∀ X : C, MulOneClass X] [∀ X Y : C, MonoidHomClass (X ⟶ Y) X Y]
(F : X.Presheaf C) where
obj : ∀ U, Submonoid (F.obj U)
map : ∀ {U V : (Opens X)ᵒᵖ} (i : U ⟶ V), obj U ≤ (obj V).comap (F.map i)
variable {F : X.Presheaf CommRingCat.{w}} (G : F.SubmonoidPresheaf)
But of course now, this no longer works because there is no MonoidHomClass
on X ⟶ Y
for X Y : CommRingCat
anymore. So either we try out Anne's BundledHomCategory
approach or do the cheap trick and specialize to CommRingCat
again.
Christian Merten (Dec 06 2024 at 09:11):
Maybe also the first point is best solved by BundledHomCategory
: delete ConcreteCategory.instFunLike
(with no replacement) and reformulate all declarations depending on it in terms of BundledHomCategory
. In this way, (forget C).map f x
no longer appears in general lemmas, but always directly the correct hom f x
.
Anne Baanen (Dec 06 2024 at 10:12):
Based on my ModuleCat
experience, that first class of issue is best fixed for now by adding a change
/have
statement to punch through the defeq between hom
and ConcreteCategory.instFunLike
all at once. (Relying on defeq to do rewriting, essentially.)
In the longer term we should find a way to get rid of the fact that we state a lot of results in the language of that instFunLike
but the actual coercions from morphisms to functions don't invoke that instance. So either we redefine ConcreteCategory
to unbundle FunLike
from it (meaning we can pass in the FunLike
that we actually want), we redefine the ConcreteCategory
instances so they at least reduce to the hom
projections, or as you say we avoid talking about the forgetful functor and use something like BundledHomCategory
instead.
Anne Baanen (Dec 06 2024 at 10:15):
I discussed something like this previously with Johan, about dealing with the mismatch between docs#CategoryTheory.Bundled.coeSort and docs#CategoryTheory.ConcreteCategory.hasCoeToSort by unbundling the coercion from ConcreteCategory
. There I concluded that this does not help enough since the coercion would get unfolded for specific categories but we'd end up with CoeSort.coe
for the generic lemmas. For coercion to functions that issue does not exist as long as we can ensure that we end up with FunLike.coe
everywhere.
Anne Baanen (Dec 06 2024 at 10:19):
For the second issue: rg '(Hom|Map)Class .* ⟶'
suggests the one you point out is the only remaining case where we want to assume morphisms satisfy a HomClass
. So if we do decide to go for specialization, that would be the last of it and we'd only have to touch those three cases (ZeroHomClass
and LinearMapClass
in the ModuleCat.Hom
refactor, and RingHomClass
now).
Anne Baanen (Dec 06 2024 at 10:41):
Anne Baanen said:
Based on my
ModuleCat
experience, that first class of issue is best fixed for now by adding achange
/have
statement to punch through the defeq betweenhom
andConcreteCategory.instFunLike
all at once. (Relying on defeq to do rewriting, essentially.)
I pushed a commit to your branch doing this for RingedSpace
, feel free to modify or revert.
Christian Merten (Dec 06 2024 at 10:48):
Anne Baanen said:
In the longer term we should find a way to get rid of the fact that we state a lot of results in the language of that
instFunLike
but the actual coercions from morphisms to functions don't invoke that instance. So either we redefineConcreteCategory
to unbundleFunLike
from it (meaning we can pass in theFunLike
that we actually want), we redefine theConcreteCategory
instances so they at least reduce to thehom
projections, or as you say we avoid talking about the forgetful functor and use something likeBundledHomCategory
instead.
I like the "modify ConcreteCategory
idea". Maybe we could add a field coeFun
with default value the one induced from (forget C).map
and a proof field that the passed coeFun
agrees with the default one.
Christian Merten (Dec 06 2024 at 10:53):
Then when working in a general setting, one could even locally add @[simp]
to the proof field.
Anne Baanen (Dec 11 2024 at 14:17):
I tried implementing the BundledHomCategory
class above to replace ConcreteCategory
(renaming the current ConcreteCategory
as HasForget
), see branch#redesign-ConcreteCategory
Anne Baanen (Dec 11 2024 at 14:17):
It seems to go smoother than introducing the Hom
structures, although I didn't get very far down (goal is to get Mathlib.Topology.Sheaves.CommRingCat
building so I can experiment with sheaves on concrete categories)
Anne Baanen (Dec 16 2024 at 15:15):
The last few days I was looking at one issue in ModuleCat.Hom
, which is that the type synonym docs#ModuleCat.restrictScalars gets unfolded more eagerly by the ofHom
constructor. That lead to missing and conflicting instances, which we worked around by adding more type hints, see for example in docs#ModuleCat.semilinearMapAddEquiv on line 132.
Inserting explicit maps between M
and (restrictScalars f).obj M
is not quite enough, since the unfolding happens by the ofHom
constructor before the toFun
field of a linear map is elaborated. So the solution seems to require making restrictScalars
irreducible, which I did by wrapping it in a structure: #19941. The problem I found with the PR is that the defeq M = (restrictScalars f).obj M
is used implicitly a lot when working with (pre)sheaves of modules over (pre)sheaves of rings, since the restriction maps on rings give rise to restrictScalars
on the modules involved. All of these implicit identifications now have to be done by explicit natural isomorphisms. In the end, I made such a mess that I don't believe this change is worth it in the current form.
Anne Baanen (Dec 16 2024 at 15:19):
One way to go forward might be to redesign (pre)sheaves of modules so they bundle the ring together with the module. If I understand it correctly, this means we remember an (explicit) isomorphism of the bundled ring in the module presheaf with the ring from the presheaf.
@Joël Riou, you appear to be the main author of sheaves of modules, do you think a refactor in this way is something we should be working towards?
Joël Riou (Dec 16 2024 at 17:32):
Anne Baanen said:
One way to go forward might be to redesign (pre)sheaves of modules so they bundle the ring together with the module. If I understand it correctly, this means we remember an (explicit) isomorphism of the bundled ring in the module presheaf with the ring from the presheaf.
Joël Riou, you appear to be the main author of sheaves of modules, do you think a refactor in this way is something we should be working towards?
No, this does not seem reasonable to me. The situation was already greatly improved when I refactored the definition to define a presheaf of modules as a bunch of objects in various ModuleCat
categories. (Making restrictScalars.obj
a little bit less prone to dsimp
, and encouraging the use of explicit tautological isomorphisms would be ok with me, but making it irreducible seems a little bit too extreme for me.)
(If someone really wants to do a refactor of presheaves of modules, the better generality would be to take an abstract pseudofunctor F
from a category J
to the 2-category of categories (e.g. given a presheaf of rings R
, this could be the pseudofunctor which sends j : J
to the category of modules over R.obj j
, along with the restriction of scalars functors, etc), so that we may define a category whose objects are families of objects in F.obj j
, with "restriction" maps, etc. Then, presheaves of modules would be a particular case of this construction. Working with a general pseudofunctor would make it impossible to "abuse" defeq.)
But, at some point, I believe we should stop doing redesigns and making more mathematical developments with the existing definitions.
Anne Baanen (Dec 17 2024 at 09:41):
Okay! Since only inserting the explicit maps doesn't help much, then I'll close #19941 as not worth it. Thanks for the detailed explanation!
Last updated: May 02 2025 at 03:31 UTC