[LINQ a través de la serie C#]
[Teoría de categorías a través de la serie C#]
Bifunción
Un funtor es el mapeo de 1 objeto a otro objeto, con una capacidad de "Seleccionar" para mapear 1 morfismo a otro morfismo. Un bifuntor (funtor binario), como su nombre lo indica, es el mapeo de 2 objetos y de 2 morfismos. Dando la categoría C, D y E, el bifunctor F de la categoría C, D a E es un morfismo que conserva la estructura de C, D a E, denotado F:C × D → E:
- F asigna objetos X ∈ ob(C), Y ∈ ob(D) al objeto F(X, Y) ∈ ob(E)
- F también mapea morfismos mC :X → X’ ∈ hom(C), mD :Y → Y’ ∈ hom(D) al morfismo mE :F(X, Y) → F(X’, Y’) ∈ hom(E)
En la categoría DotNet, los bifuntores son endofuntores binarios y se pueden definir como:
// Cannot be compiled. public interface IBifunctor<TBifunctor<,>> where TBifunctor<,> : IBifunctor<TBifunctor<,>> { Func<TBifunctor<TSource1, TSource2>, TBifunctor<TResult1, TResult2>> Select<TSource1, TSource2, TResult1, TResult2>( Func<TSource1, TResult1> selector1, Func<TSource2, TResult2> selector2); }
El bifuntor integrado más intuitivo es ValueTuple<,>. Aparentemente, ValueTuple<,> puede verse como un constructor de tipo de tipo * –> * –> *, que acepta 2 tipos concretos y devuelve otro tipo concreto. Su implementación Select también es sencilla:
public static partial class ValueTupleExtensions // ValueTuple<T1, T2> : IBifunctor<ValueTuple<,>> { // Bifunctor Select: (TSource1 -> TResult1, TSource2 -> TResult2) -> (ValueTuple<TSource1, TSource2> -> ValueTuple<TResult1, TResult2>). public static Func<ValueTuple<TSource1, TSource2>, ValueTuple<TResult1, TResult2>> Select<TSource1, TSource2, TResult1, TResult2>( Func<TSource1, TResult1> selector1, Func<TSource2, TResult2> selector2) => source => Select(source, selector1, selector2); // LINQ-like Select: (ValueTuple<TSource1, TSource2>, TSource1 -> TResult1, TSource2 -> TResult2) -> ValueTuple<TResult1, TResult2>). public static ValueTuple<TResult1, TResult2> Select<TSource1, TSource2, TResult1, TResult2>( this ValueTuple<TSource1, TSource2> source, Func<TSource1, TResult1> selector1, Func<TSource2, TResult2> selector2) => (selector1(source.Item1), selector2(source.Item2)); }
Sin embargo, de forma similar al método Select del functor ValueTuple<>, el método Select del bifunctor ValueTuple<,> tiene que llamar a selector1 y selector2 inmediatamente. Para implementar la ejecución diferida, se puede definir el siguiente bifunctor Lazy<,>:
public class Lazy<T1, T2> { private readonly Lazy<(T1, T2)> lazy; public Lazy(Func<(T1, T2)> factory) => this.lazy = new Lazy<(T1, T2)>(factory); public T1 Value1 => this.lazy.Value.Item1; public T2 Value2 => this.lazy.Value.Item2; public override string ToString() => this.lazy.Value.ToString(); }
Lazy<,> es simplemente la versión perezosa de ValueTuple<,>. Al igual que Lazy<>, Lazy<,> se puede construir con una función de fábrica, de modo que la llamada a selector1 y selector2 se difiera:
public static partial class LazyExtensions // Lazy<T1, T2> : IBifunctor<Lazy<,>> { // Bifunctor Select: (TSource1 -> TResult1, TSource2 -> TResult2) -> (Lazy<TSource1, TSource2> -> Lazy<TResult1, TResult2>). public static Func<Lazy<TSource1, TSource2>, Lazy<TResult1, TResult2>> Select<TSource1, TSource2, TResult1, TResult2>( Func<TSource1, TResult1> selector1, Func<TSource2, TResult2> selector2) => source => Select(source, selector1, selector2); // LINQ-like Select: (Lazy<TSource1, TSource2>, TSource1 -> TResult1, TSource2 -> TResult2) -> Lazy<TResult1, TResult2>). public static Lazy<TResult1, TResult2> Select<TSource1, TSource2, TResult1, TResult2>( this Lazy<TSource1, TSource2> source, Func<TSource1, TResult1> selector1, Func<TSource2, TResult2> selector2) => new Lazy<TResult1, TResult2>(() => (selector1(source.Value1), selector2(source.Value2))); }
Categoría monoide
Con la ayuda de bifunctor, se puede definir la categoría monoide. Una categoría monoide es una categoría C equipada con:
- Un bifuntor ⊗ como la operación de multiplicación binaria monoide:el bifunctor ⊗ asigna 2 objetos en C a otro objeto en C, denotado C ⊗ C → C, que también se denomina producto monoide o producto tensorial.
- Un objeto unidad I ∈ ob(C) como unidad monoide, también llamada unidad tensor
Para que (C, ⊗, I) sea un monoide, también debe estar equipado con las siguientes transformaciones naturales, de modo que se cumplan las leyes del monoide:
- Asociador αX, Y, Z :(X ⊗ Y) ⊗ Z ⇒ X ⊗ (Y ⊗ Z) para la ley de asociatividad, donde X, Y, Z ∈ ob(C)
- Unitor izquierdo λX :I ⊗ X ⇒ X para la ley unitaria izquierda, y unitor derecha ρX :X ⊗ I ⇒ X para la ley unitaria derecha, donde X ∈ ob(C)
Los siguientes diagramas de identidad del triángulo monoide y del pentágono todavía conmutan para la categoría monoide:
Aquí, para la categoría monoide, el ⊙ anterior (operador de multiplicación general) se convierte en ⊗ (bifuntor).
La categoría monoide se puede definir simplemente como:
public interface IMonoidalCategory<TObject, TMorphism> : ICategory<TObject, TMorphism>, IMonoid<TObject> { }
La categoría DotNet es una categoría monoide, con el bifunctor más intuitivo ValueTuple<,> como la multiplicación monoide, y el tipo de Unidad como la unidad monoide:
public partial class DotNetCategory : IMonoidalCategory<Type, Delegate> { public Type Multiply(Type value1, Type value2) => typeof(ValueTuple<,>).MakeGenericType(value1, value2); public Type Unit() => typeof(Unit); }
Para que (DotNet, ValueTuple<,>, Unit) satisfagan las leyes monoides, el asociador, el unitor izquierdo y el unitor derecho son fáciles de implementar:
public partial class DotNetCategory { // Associator: (T1 x T2) x T3 -> T1 x (T2 x T3) // Associator: ValueTuple<ValueTuple<T1, T2>, T3> -> ValueTuple<T1, ValueTuple<T2, T3>> public static (T1, (T2, T3)) Associator<T1, T2, T3>(((T1, T2), T3) product) => (product.Item1.Item1, (product.Item1.Item2, product.Item2)); // LeftUnitor: Unit x T -> T // LeftUnitor: ValueTuple<Unit, T> -> T public static T LeftUnitor<T>((Unit, T) product) => product.Item2; // RightUnitor: T x Unit -> T // RightUnitor: ValueTuple<T, Unit> -> T public static T RightUnitor<T>((T, Unit) product) => product.Item1; }