Zulip Chat Archive
Stream: lean4
Topic: Unable to prove seemingly trivial naturality
Leorasz (Nov 11 2025 at 13:54):
Hi guys, I'm a beginner to Lean4 and category theory and was trying to contribute to an existing project but I'm stuck at a certain point trying to prove naturality between product (with extra structure) and coproduct. Here's my code right now :
import Mathlib.Algebra.Category.ModuleCat.Abelian
import Mathlib.Algebra.Category.ModuleCat.Biproducts
import Mathlib.RingTheory.Artinian.Module
import Mathlib.LinearAlgebra.FreeModule.Finite.Basic
import Mathlib.LinearAlgebra.Dimension.StrongRankCondition
import Mathlib.LinearAlgebra.FiniteDimensional.Basic
open CategoryTheory Filter Module
universe u
def ZeroSubmod (K : Type u) [DivisionRing K] : ModuleCat.{u} K := .of _ PUnit.{u+1}
def ZeroModule (C K : Type*) [Category C] [DivisionRing K] : C ⥤ ModuleCat K where
obj _ := ZeroSubmod K
map _ := 𝟙 (ZeroSubmod K)
--Pointwise finite persistence modules over some small category C.
structure PtwiseFinitePersMod (C : Type*) [Category C] (K : Type*)
[DivisionRing K] where
to_functor : C ⥤ ModuleCat K
finite_cond (X : C) : Module.Finite K (to_functor.obj X)
--Below : product of persistence modules
--Given R a field, C a category, X ∈ Obj(C) and F, G two functors
--C ⥤ Modules over R, this defines the product F(X) × G(X).
@[simp]
def ProductModule (R : Type) [DivisionRing R] (C : Type) [Category C]
(F : C ⥤ ModuleCat R) (G : C ⥤ ModuleCat R) (X : C): ModuleCat R :=
ModuleCat.of R ((F.obj X) × (G.obj X))
--Given R a field, C a category, X, Y ∈ Obj(C), f : X ⟶ Y a linear map
--and F, G two functors C ⥤ Modules over R, this defines the product map
--(F f, G f) : (F X × G X) ⟶ (F Y × G Y)
@[simp]
def ProductMapFunc (R : Type) [DivisionRing R] (C : Type) [Category C]
{X Y : C} (f : (X ⟶ Y)) (F : C ⥤ ModuleCat R) (G : C ⥤ ModuleCat R)
: ((F.obj X × G.obj X) →ₗ[R] (F.obj Y × G.obj Y)) where
toFun x := by
let x₁ := x.1
let x₂ := x.2
exact ⟨F.map f x₁, G.map f x₂⟩
map_add' x y := by
dsimp
rw [LinearMap.map_add, LinearMap.map_add]
map_smul' c x := by
dsimp
rw [LinearMap.map_smul, LinearMap.map_smul]
--Same as above, but written with the ProductModule objects for simplicity
@[simp]
def ProductModuleMap (R : Type) [DivisionRing R] (C : Type) [Category C]
{X Y : C} (f : (X ⟶ Y)) (F : C ⥤ ModuleCat R) (G : C ⥤ ModuleCat R) :
((ProductModule R C F G X) ⟶ (ProductModule R C F G Y)) :=
ModuleCat.ofHom <| ProductMapFunc R C f F G
--The direct sum of the functors F and G.
@[simp]
noncomputable def FunctorDirSum (R : Type) [DivisionRing R] (C : Type) [Category C]
(F : C ⥤ ModuleCat R) (G : C ⥤ ModuleCat R) : C ⥤ ModuleCat R where
obj X := ProductModule R C F G X
map f := ProductModuleMap R C f F G
map_id := by
intro X
simp
rfl
map_comp := by
intro X Y Z f g
simp
rfl
set_option maxHeartbeats 0
noncomputable def componentIso (R : Type) [DivisionRing R] (C : Type) [Category C]
(F : C ⥤ ModuleCat R) (G : C ⥤ ModuleCat R) (X : C) :
ProductModule R C F G X ≅ (F ⨿ G).obj X := by
let iso1 := (ModuleCat.biprodIsoProd (F.obj X) (G.obj X)).symm
let iso2 := Limits.biprod.isoCoprod (F.obj X) (G.obj X)
let iso3 : F.obj X ⨿ G.obj X ≅ (F ⨿ G).obj X := by
have h := preservesColimitIso ((evaluation C (ModuleCat R)).obj X) (Limits.pair F G)
have h2 := Limits.HasColimit.isoOfNatIso
(Limits.pairComp F G ((evaluation C (ModuleCat R)).obj X))
exact h2.symm ≪≫ h.symm
exact iso1 ≪≫ iso2 ≪≫ iso3
lemma ModuleCat.eq_of_hom_eq (R : Type) [DivisionRing R] (M N : ModuleCat R) (f g : M ⟶ N) (h : f.hom = g.hom) : f = g := by
exact hom_ext h
--The above-defined direct sum of persistence modules corresponds to the
--coproduct inherited from the fact that ModuleCat is abelian.
noncomputable def DirSumIsoCoprod (R : Type) [DivisionRing R] (C : Type) [Category C]
(F : C ⥤ ModuleCat R) (G : C ⥤ ModuleCat R) :
(FunctorDirSum R C F G) ≅ (F ⨿ G) :=
NatIso.ofComponents (fun X => componentIso R C F G X)
(fun {Y Z} f => by
--other option
apply ModuleCat.eq_of_hom_eq
ext ⟨x₁, x₂⟩
sorry
)
As you can see I've proven the basic isomorphism but I'm stuck at naturality, I have the start of something with the hom and maybe doing linear algebra stuff there but I couldn't get anywhere with it. I've been working probably a dozen hours at this point trying to figure out how to do this one part of the proof but so far I've come up with nothing. I've tried all kinds of simps and then rfl but it never gets anywhere, and I still can't figure out a way to break the problem down at all to prove smaller pieces. It seems like there should be some tactic or theorem that can simplify this and I just don't have the experience to know to use it, so I was wondering if someone could even just give me a start on the problem. Any help would be greatly appreciated
Robert Maxton (Nov 12 2025 at 02:36):
Minor things, conveniences and general good practice:
- First, just as a matter of convenience/clarity, you can combine adjacent variable declarations of the same type, so e.g. you can just write `(F G : C ⥤ ModuleCat R)
- Similarly, you can 'factor out' variable declarations that are repeated across many definitions by putting e.g.
variable (R : Type) [DivisionRing R] (C : Type) [Category C]on its own line, and it'll persist until the end of the section, namespace, or file. - Especially when starting out, you should usually avoid building data (anything that isn't a proving a
Prop) using tactics. Sometimes there's no real other option, but you should generally try without tactics first, then try usingby?which tries to turn what you've built using tactics into a non-tactic term, and only then leavebyin the final result.ProductMapFunc.toFunis simple enough that you're getting away with it, but it's also simple enough that it can be easily replaced with justtoFun x := (F.map f x.1, G.map f x.2). - Finally, you probably should be using the
simpsattribute instead ofsimpon most of these definitions;simpsattempts to automatically provide a set of simp lemmas for e.g. applications of funlikes and projections/fields of types, which is usually better than a single simp lemma equating just the name of the definition with its body. This is particularly true in category theory where we try even harder than normal not to unfold definitions
Robert Maxton (Nov 12 2025 at 02:46):
With regards to your actual question: The category theory library is built heavily on abstractly defined constructions. By this I mean that, for example, F ⨿ G isn't really a concrete type, but rather the result of a nonconstructive proof of HasBinaryCoproduct F G, which essentially means ∃ M : C ⥤ ModuleCat R, M satisfies the universal property of the binary coproduct for the functors F and G.
You may already know that if you extract the witness x from an existential ∃ x, p x using choice, you know nothing about x except p x. It follows that you know nothing about, say, the biproduct F.obj X ⊞ G.obj X except for its universal properties, and that you know nothing about the various morphisms that involve it except their universal properties -- you can't "unwrap" them into a concrete definition, because there is no concrete definition.
This means that, even more so than in the rest of Mathlib, definitions should usually be followed by as much of the relevant API lemmas as you can think of, because there's otherwise no good way to interact with your definition.
Robert Maxton (Nov 12 2025 at 02:49):
While in your case, ProductModule and fellows are concretely defined, the rest of the category theory library is expecting various categorical projections and lemmas about how those projections behave when composed that you haven't defined yet, so it's hard to make use of.
Specifically, you need:
- The universal property of the product: the type of homs out of
ProductModule F G Xare in bijection with pairs of homs out ofF.obj XandG.obj X. In particular, it follows that
@[ext]
lemma ProductModule.hom_ext {R : Type} [DivisionRing R] {C : Type} [Category C]
{F G : C ⥤ ModuleCat R} {X : C} {M : ModuleCat R} {f g : ProductModule R C F G X ⟶ M}
(h₁ : ModuleCat.ofHom (LinearMap.fst R (F.obj X) (G.obj X)) ≫ f =
ModuleCat.ofHom (LinearMap.fst R (F.obj X) (G.obj X)) ≫ g)
(h₂ : ModuleCat.ofHom (LinearMap.snd R (F.obj X) (G.obj X)) ≫ f =
ModuleCat.ofHom (LinearMap.snd R (F.obj X) (G.obj X)) ≫ g) : f = g := by
sorry
(Registering this lemma as ext lets ext take apart all such homs for you automatically, so you shouldn't even need to explicitly invoke this in DirSumIsoCoprod.)
- Similarly,
ProductModuleMapcommutes with the projections:ProductModuleMap F G f ≫ ofHom (fst R (F.obj Y) (G.obj Y) ) = ofHom (fst R (F.obj X) (G.obj X))and similarly forsnd.
Once you have that, you should be able to make progress on DirSumIsoCoprod.
Last updated: Dec 20 2025 at 21:32 UTC