Category Theory via C# (10) Monoidal Category
[LINQ via C# series]
[Category Theory via C# series]
Latest version: https://weblogs.asp.net/dixin/category-theory-via-csharp-6-monoidal-functor-and-applicative-functor
Monoidal category
A previous part demonstrated endofunctor category is monoidal. Now with the help of bifunctor, the general abstract monoidal category can be defined. A monoidal category is a category C equipped with:
- A bifunctor ⊗: C ⊗ C → C, as the monoid binary operation, also called the monoidal product
- An unit object I ∈ C as the monoid unit
- A natural transformation λX: I ⊗ X ⇒ X, called left unitor
- A natural transformation ρX: X ⊗ I ⇒ X, called right unitor
- A natural transformation αX, Y, Z: (X ⊗ Y) ⊗ Z ⇒ X ⊗ (Y ⊗ Z), called associator
so that C satisfies the monoid laws:
- Left unit law λX: I ⊗ X ⇒ X (according to definition)
- and right unit law ρX: X ⊗ I ⇒ X (definition)
- Associative law αX, Y, Z: (X ⊗ Y) ⊗ Z ⇒ X ⊗ (Y ⊗ Z) (definition)
The following triangle identity and pentagon identity diagrams copied from the monoid part still commute for monoidal category:
Just read the ⊙ (general binary operator) as ⊗ (bifunctor).
The existence of bifunctor ⊗ makes it possible to ⊗ (can be read as multiply) any 2 elements in the category, and get another element still in the category (the Cartesian product represented by that bifunctor). So, bifunctor ⊗ and unit I forms the monoid structure of the category, and the 3 natural transformations make sure this binary “multiply” operation satisfies the monoidal rules:
- left unit law: λX(I ⊗ X) ≌ X
- right unit law: ρX(X ⊗ I) ≌ X
- associative law: αX, Y, Z((X ⊗ Y) ⊗ Z) ≌ X ⊗ (Y ⊗ Z)
In pseudo C#:
public interface IMonoidalCategory<TMonoidalCategory, out TBinaryFunctor< , >> : ICategory<TMonoidalCategory> where TBinaryFunctor< , > : IBinaryFunctor<TMonoidalCategory, TMonoidalCategory, TMonoidalCategory, TBinaryFunctor< , >> { TBinaryFunctor<T1, T2> x<T1, T2>(T1 value1, T2 value2); }
DotNet category is monoidal category
In above definition, x represents ⊗ (multiple). However, this cannot be expressed in real C# because IBinaryFunctor<…> is involved, which requires C# language to have higher-kinded polymorphism:
// Cannot be compiled. public interface IBinaryFunctor<in TSourceCategory1, in TSourceCategory2, out TTargetCategory, TBinaryFunctor< , >> where TSourceCategory1 : ICategory<TSourceCategory1> where TSourceCategory2 : ICategory<TSourceCategory2> where TTargetCategory : ICategory<TTargetCategory> where TBinaryFunctor< , > : IBinaryFunctor<TSourceCategory1, TSourceCategory2, TTargetCategory, TBinaryFunctor< , >> { IMorphism<TBinaryFunctor<TSource1, TSource2>, TBinaryFunctor<TResult1, TResult2>, TTargetCategory> Select<TSource1, TSource2, TResult1, TResult2>( IMorphism<TSource1, TResult1, TSourceCategory1> selector1, IMorphism<TSource2, TResult2, TSourceCategory2> selector2); }
So, just like the functor and bifunctor, go with the extension method approach.
For DotNet category, the bifunctor can be Lazy< , >. So:
[Pure] public static class DotNetExtensions { public static Lazy<T1, T2> x<T1, T2> (this DotNet category, T1 value1, T2 value2) => new Lazy<T1, T2>(() => value1, () => value2); }
To be more intuitive, the following “x” extension method can be created for elements in DotNet category:
// [Pure] public static partial class LazyExtensions { public static Lazy<T1, T2> x<T1, T2> (this T1 value1, T2 value2) => new Lazy<T1, T2>(value1, value2); }
so that the multiplication binary operation can be applied with any 2 elements in DotNet category, and result another element in DotNet category - the Cartesian product represented by Lazy< , > bifunctor:
var x = 1.x(true); var y = "abc".x(2).x(new HttpClient().x((Unit)null)); var z = y.x(typeof(Unit));
This demonstrates the monoidal structure of DotNet category.
Next, the 3 natural transformations can be implemented as bifunctor’s extension methods too, by borrowing Microsoft.FSharp.Core.Unit from F# as the unit:
// [Pure] public static partial class LazyExtensions { public static T2 LeftUnit<T2> (this Lazy<Unit, T2> product) => product.Value2; public static T1 RightUnit<T1> (this Lazy<T1, Unit> product) => product.Value1; public static Lazy<T1, Lazy<T2, T3>> Associate<T1, T2, T3> (Lazy<Lazy<T1, T2>, T3> product) => new Lazy<T1, Lazy<T2, T3>>( () => product.Value1.Value1, () => new Lazy<T2, T3>(() => product.Value1.Value2, () => product.Value2)); }
So, with Lazy< , > as bifunctor, F# unit as C# unit, plus above 3 natural transformations, DotNet category is a monoidal category (DotNet, Lazy< , >, Unit).