feat(CategoryTheory/Profunctor): Add profunctors and a basic API for them#35970
feat(CategoryTheory/Profunctor): Add profunctors and a basic API for them#35970adrianmartir wants to merge 8 commits into
Conversation
Welcome new contributor!Thank you for contributing to Mathlib! If you haven't done so already, please review our contribution guidelines, as well as the style guide and naming conventions. We use a review queue to manage reviews. If your PR does not appear there, it is probably because it is not successfully building (i.e., it doesn't have a green checkmark), has the If you haven't already done so, please come to https://leanprover.zulipchat.com/, introduce yourself, and mention your new PR. Thank you again for joining our community. |
PR summary b9242563f9Import changes for modified filesNo significant changes to the import graph Import changes for all files
|
f83cedc to
a44917d
Compare
|
The convention I like best says that a profunctor from The one challenge with this convention is the ordering of variables in the object mapping So these would be my concrete suggestions about conventions but others should of course weigh in too. |
|
Let me suggest a few examples that should appear already in this file as a test of the API:
Note these restrictions have a universal property that we can discuss if you like. |
|
|
||
| universe w | ||
|
|
||
| /-- A profunctor between two categories `C` and `D` is a functor from `Cᵒᵖ × D` to the category of |
There was a problem hiding this comment.
The comment seems to mismatch the text below. I'd write
A profunctor between two categories C and D is a functor from Dᵒᵖ × C ....
This is actually my preferred convention!
There was a problem hiding this comment.
I fixed this, I was a little to quick when writing that docstring :)
| /-- Apply a profunctor to a pair of objects. -/ | ||
| obj : D → C → Type w | ||
| /-- Apply a profunctor to a pair of maps. -/ | ||
| map {X X' : D} {Y Y' : C} (f : X' ⟶ X) (g : Y ⟶ Y') : |
There was a problem hiding this comment.
Emphasizing the analogy between profunctors and bimodules, it would be great if you could use infix notation and writing something like f . e . g for map f g e using some sort of special composition or action symbol. What is used for actual bimodules?
| (f' : X'' ⟶ X') (f : X' ⟶ X) | ||
| (g : Y ⟶ Y') (g' : Y' ⟶ Y'') | ||
| (e : obj X Y) : | ||
| map (f' ≫ f) (g ≫ g') e = map f' g' (map f g e) |
There was a problem hiding this comment.
Note in the suggested notation above this becomes (f' ≫ f) . e . (g ≫ g') = f' . (f . e . g) . g'.
There was a problem hiding this comment.
I will look into bimodules, I didn't think at all about notation yet. However, I'm not sure whether it is actually possible to declare a notation that is used in the structure definition itself already.
|
Thank you for taking your time to look at the code! I agree that the code will need some testing with examples and also some experimenting with automation, since it was fairly quick to write. I am actually also working on a module with actions of functors on profunctors ("restrictions") which already contains all sorts of functoriality and action compatibility properties. This is needed for operads. I can also add the hom profunctor and also a constructor for a profunctor on a pair of discrete categories (I need this too). For the hom profunctor, syncronizing with 'CategoryTheory.Functor.Hom' might also be important. So there is a lot that can be added. Let me know how much of it would be a good amount for a first PR. Alternatively, this profunctors code can also be tested a bit in another repo (eventually including additional content like universal properties) to show that it works. |
174af41 to
b27a52d
Compare
|
As you know, @adamtopaz and I are developing profunctors in a private repo. We have API analogous to yours and the glue to go back and forth between profunctors as the custom structure and as functors from |
|
@dagurtomas I'd love to hear more about the problems with the current category instance and how your refactor fixes it. When you have a momment could you explain or point me to a discussion on the zulip that I may have missed? |
|
I guess the category of types specifically has not been discussed much publically on Zulip, but there have been discussions in the reviewer's channel (which was probably not the right place, there is nothing wrong with such discussions taking place in public). But really it's the same issue as with concrete categories in general (see e.g. #mathlib4 > Concrete category class redesign), and full subcategories, induced categories, etc. When you have some type of objects and types of "morphisms" between them (say, groups and group homomorphisms), your instinct is to define a category structure on the type of groups, where the type This has been done now for all the standard concrete categories of algebraic structures and topological spaces. The same approach of one-field structures also works very well for e.g. full subcategories. Previously, the type of morphisms in the full subcategory was definitionally equal to the type of morphisms in the ambient category, but this tended to make automation confused. This has now also been refactored so that the hom set in the full subcategory is a one-field structure with the field being the hom set in tha ambient category. Because of how bad automation worked, the full subcategory API was rarely used before the refactor, e.g. the category of sheaves was defined as a custom structure with one-field structure homs instead of a full subcategory of presheaves. But earlier today, I delegated #36081 which relatively easily switched over to the full subcategory API. Anyway, the category of types has been left unchanged, with |
|
I am skeptical about the definition as a structure: I would favour defining profunctors as an abbrev for a category of bifunctors |
|
Just using I would love to coordinate the development of profunctors! I contacted Adam some time ago so that we can do this, but he hasn't gotten around to replying. |
|
I added some more stuff to the profunctors code on a separate repo. There is a file in there on the hom profunctor and a file on actions of functors on profunctors, as discussed above. Disclaimer: I'm experimenting with using AI for API design. Some of the code in there is WIP (mainly the code on cells) and needs some careful review and some refactoring. Some might say that using AI for this is a terrible idea, but so far I have been pleasantly surprised. |
|
Can we remove the 'awaiting-author' label? I'm not sure that there is something that I need to do. If anything, we should agree on whether this is the right design of profunctors for mathlib or no. |
I would stand with my position that we should just use an abbrev for a category of (currified) bifunctors. The ongoing refactor of the category of |
|
Where is the refactor of |
|
#36613 is the refactor of the category of types |
|
Now that the refactor of the category of types is merged, I've had some time to think about profunctors again. I think I agree with Joël now that we should just make import Mathlib.CategoryTheory.Yoneda
namespace CategoryTheory
open Opposite
universe w
variable (C D : Type*) [Category* C] [Category* D]
@[pp_with_univ]
structure ProfunctorCore where
obj : C → D → Type w
map {X X' : C} {Y Y' : D} (f : X ⟶ X') (g : Y ⟶ Y') : obj X Y' ⟶ obj X' Y
map_id (X : C) (Y : D) : map (𝟙 X) (𝟙 Y) = 𝟙 _ := by cat_disch
map_comp {X₁ X₂ X₃ : C} {Y₁ Y₂ Y₃ : D} (f : X₁ ⟶ X₂) (f' : X₂ ⟶ X₃) (g : Y₁ ⟶ Y₂) (g' : Y₂ ⟶ Y₃) :
map (f ≫ f') (g ≫ g') = map f g' ≫ map f' g := by cat_disch
attribute [local simp] ProfunctorCore.map_id ProfunctorCore.map_comp
variable {C D}
@[local simp]
lemma ProfunctorCore.map_id_comp (P : ProfunctorCore.{w} C D) (X : C) {Y Y' Y'' : D}
(g : Y ⟶ Y') (g' : Y' ⟶ Y'') :
P.map (𝟙 X) (g ≫ g') = P.map (𝟙 X) g' ≫ P.map (𝟙 X) g := by
nth_rw 1 [← Category.id_comp (𝟙 X)]
simp only [P.map_comp]
@[local simp]
lemma ProfunctorCore.map_comp_id (P : ProfunctorCore.{w} C D) {X X' X'' : C} (Y : D)
(f : X ⟶ X') (f' : X' ⟶ X'') :
P.map (f ≫ f') (𝟙 Y) = P.map f (𝟙 Y) ≫ P.map f' (𝟙 Y) := by
nth_rw 1 [← Category.id_comp (𝟙 Y)]
simp only [P.map_comp]
@[reassoc (attr := local simp)]
lemma ProfunctorCore.map_lid_comp_map_rid (P : ProfunctorCore.{w} C D) {X X' : C} {Y Y' : D}
(f : X ⟶ X') (g : Y ⟶ Y') : P.map (𝟙 _) g ≫ P.map f (𝟙 _) = P.map f g := by
simp [← P.map_comp]
@[reassoc (attr := local simp)]
lemma ProfunctorCore.map_rid_comp_map_lid (P : ProfunctorCore.{w} C D) {X X' : C} {Y Y' : D}
(f : X ⟶ X') (g : Y ⟶ Y') : P.map f (𝟙 _) ≫ P.map (𝟙 _) g = P.map f g := by
simp [← P.map_comp]
variable (C D) in
/--
A profunctor from C to D is a bifunctor `D^op × C -> Type`. We define it in its curried form,
i.e. as `C ⥤ Dᵒᵖ ⥤ Type`.
-/
@[pp_with_univ]
abbrev Profunctor := C ⥤ Dᵒᵖ ⥤ Type w
@[simps]
def Profunctor.ofCore (P : ProfunctorCore.{w} C D) : Profunctor.{w} C D where
obj X := { obj Y := P.obj X (unop Y), map f := P.map (𝟙 _) f.unop }
map g := { app X := P.map g (𝟙 _) }
abbrev Profunctor.obj₂ (P : Profunctor.{w} C D) (X : C) (Y : D) : Type w := (P.obj X).obj (op Y)
abbrev Profunctor.map₂ (P : Profunctor.{w} C D) {X X' : C} {Y Y' : D} (f : X ⟶ X') (g : Y ⟶ Y') :
P.obj₂ X Y' ⟶ P.obj₂ X' Y := (P.map f).app (op Y') ≫ (P.obj X').map g.op
lemma Profunctor.map₂_def (P : Profunctor.{w} C D) {X X' : C} {Y Y' : D} (f : X ⟶ X')
(g : Y ⟶ Y') : P.map₂ f g = (P.map f).app (op Y') ≫ (P.obj X').map g.op :=
rfl
lemma Profunctor.map₂_naturality_symm (P : Profunctor.{w} C D) {X X' : C} {Y Y' : D} (f : X ⟶ X')
(g : Y ⟶ Y') : P.map₂ f g = (P.obj X).map g.op ≫ (P.map f).app (op Y) := by
simp
lemma Profunctor.map_lid_comp_map_rid_apply (P : Profunctor.{w} C D) {X X' : C} {Y Y' : D}
(f : X ⟶ X') (g : Y ⟶ Y') (x : P.obj₂ X Y') :
P.map₂ f (𝟙 _) (P.map₂ (𝟙 _) g x) = P.map₂ f g x := by
simp
lemma Profunctor.map_rid_comp_map_lid_apply (P : Profunctor.{w} C D)
{X X' : C} {Y Y' : D} (f : X ⟶ X') (g : Y ⟶ Y') (x : P.obj₂ X Y') :
P.map₂ (𝟙 _) g (P.map₂ f (𝟙 _) x) = P.map₂ f g x := by
simp
example : Category (Profunctor.{w} C D) := inferInstance
example (P : Cᵒᵖ ⥤ D ⥤ Type w) : Profunctor.{w} D C := P.flip
@[simps! obj_obj obj_map map_app]
protected def Profunctor.yoneda : Profunctor C C := yoneda
def Profunctor.op (P : Profunctor.{w} C D) : Profunctor.{w} Dᵒᵖ Cᵒᵖ :=
.ofCore {
obj X Y := P.obj₂ (Opposite.unop Y) (Opposite.unop X)
map f g := P.map₂ g.unop f.unop }
@[simps! obj_obj obj_map map_app]
def Profunctor.whiskeringLeft {A B : Type*} [Category* A] [Category* B]
(P : Profunctor.{w} C D) (F : A ⥤ C) (G : B ⥤ D) : Profunctor.{w} A B :=
(((Functor.whiskeringLeft₂ _).obj F).obj G.op).obj P
@[simps! obj_obj obj_map map_app]
def Functor.toProfunctor (F : C ⥤ D) : Profunctor C D :=
(Profunctor.yoneda (C := D)).whiskeringLeft F (𝟭 _)
@[simps! obj_obj obj_map map_app]
def Functor.toProfunctorFlip (F : C ⥤ D) : Profunctor D C :=
(Profunctor.yoneda (C := D)).whiskeringLeft (𝟭 _) F
end CategoryTheory |
|
Here is a quick definition of profunctor composition using the code above: universe w v u
variable {C : Type*} {D : Type w} {E : Type*} [Category* C] [Category* D] [Category* E]
inductive Profunctor.compRel (F : Profunctor.{v} C D)
(G : Profunctor.{u} D E) (X : C) (Y : E) :
(W : D) × F.obj₂ X W × G.obj₂ W Y → (W : D) × F.obj₂ X W × G.obj₂ W Y → Prop where
| mk {W W' : D} (f : W ⟶ W') (x : F.obj₂ X W') (y : G.obj₂ W Y) :
compRel F G X Y ⟨_, (F.map₂ (𝟙 X) f) x, y⟩ ⟨_, x, (G.map₂ f (𝟙 Y)) y⟩
def Profunctor.comp (F : Profunctor.{v} C D) (G : Profunctor.{u} D E) :
Profunctor.{max u v w} C E := .ofCore {
obj X Y := Quot <| Profunctor.compRel F G X Y
map f g := TypeCat.ofHom (fun x ↦ x.liftOn
(fun ⟨W, a, b⟩ ↦ .mk _ ⟨W, (F.map₂ f (𝟙 _)) a, (G.map₂ (𝟙 _) g) b⟩) <| by
rintro ⟨W, a, b⟩ ⟨W', a', b'⟩ ⟨h, x, y⟩
apply Quot.sound
have : F.compRel G _ _ ⟨W, ((F.map₂ f (𝟙 W') ≫ F.map₂ (𝟙 _) h) a', (G.map₂ (𝟙 W) g) b)⟩
⟨W', ((F.map₂ f (𝟙 W')) a', (G.map₂ (𝟙 W) g ≫ G.map₂ h (𝟙 _)) b)⟩ :=
⟨h, (F.map₂ f (𝟙 _)) a', (G.map₂ (𝟙 _) g) b⟩
simpa using this)
map_id _ _ := by ext ⟨⟩; simp
map_comp _ _ _ _ := by ext ⟨⟩; simp }However, the correct way to go is to define explicit coends in the category of types and use that for the definition of composition. I'm working on this |
|
Thanks for your work on this @adrianmartir ! I see that there is now a separate PR that implements profunctors (#38085), which also accounts for @joelriou 's comments, and it seems that you are listed as a coauthor on the file from that PR. I will therefore close this PR, and assume that further work on profunctors takes place in #38085. |
|
I'm happy to see that getting profunctors into mathlib is progressing :) @dagurtomas , I'm not sure that using coends here is really better, since the quotient construction is more explicit. I've been also doing something really similar when formalizing operads. First, I defined a profunctor, which on objects The quotient relation just states what a compatible map really is. Of course, in the end, this universal property is the same as the universal property of the coend and both constructions are non-computable, but I would argue that the coend is less transparent and I also think it's less useful for the purposes I had in mind. Here is the n-ary version of the profunctor tensor product that I defined based on an inductive quotient relation. |
|
I agree we need to be explicit here, which is why I'm defining explicit coends in Do you already have the bicategory structure on your |
This adds a definition of a profunctor and the definition of a natural transformation between two profunctors. Profunctors are defined as a structure, as suggested by @adamtopaz on Zulip.
Using this as a base, I wrote a basic API for profunctors. This will be needed for a future formalization of operads.
A lot of the API was first drafted by @Aristotle-Harmonic.
Co-authored-by: Aristotle (Harmonic) aristotle-harmonic@harmonic.fun