Antimatroid, The

thoughts on computer science, electronics, mathematics

Abstract Algebra in C#

Motivation

In C++ it is easy to define arbitrary template methods for computations involving primitive numeric types because all types inherently have arithmetic operations defined. Thus, a programmer need only implement one method for all numeric types. The compiler will infer the use and substitute the type at compile time and emit the appropriate machine instructions. This is C++’s approach to parametric polymorphism.

With the release of C# 2.0 in the fall of 2005, the language finally got a taste of parametric polymorphism in the form of generics. Unfortunately, types in C# do not inherently have arithmetic operations defined, so methods involving computations must use ad-hoc polymorphism to achieve the same result as in C++. The consequence is a greater bloat in code and an increased maintenance liability.

To get around this design limitation, I’ve decided to leverage C#’s approach to subtype polymorphism and to draw from Abstract Algebra to implement a collection of interfaces allowing for C++ like template functionality in C#. The following is an overview of the mathematical theory used to support and guide the design of my solution. In addition, I will present example problems from mathematics and computer science that can be represented in this solution along with examples how type agnostic computations that can be performed using this solution.

Abstract Algebra

Abstract Algebra is focused on how different algebraic structures behave in the presence of different axioms, operations and sets. In the following three sections, I will go over the fundamental sub-fields and how they are represented under the solution.

In all three sections, I will represent the distinction between algebraic structures using C# interfaces. The type parameters on these interfaces represent the sets being acted upon by each algebraic structure. This convention is consistent with intuitionistic (i.e., Chruch-style) type theory embraced by C#’s Common Type System (CTS). Use of parameter constraints will be used when type parameters are intended to be of a specific type. Functions on the set and elements of the set will be represented by methods and properties respectively.

Group Theory

Group Theory is the simplest of sub-fields of Abstract Algebra dealing with the study of a single binary operation, $(\cdot)$, acting on a set $a, b, c \in S$. There are five axioms used to describe the structures studied under Group Theory:

1. Closure: $(\cdot) : S \times S \to S$
2. Associativity: $(a \cdot b) \cdot c = a \cdot (b \cdot c)$
3. Commutativity : $a \cdot b = b \cdot a$
4. Identity: $a \cdot e = e \cdot a$
5. Inverse: $a \cdot b = e$

The simplest of these structures is the Groupoid satisfying only axiom (1). Any Groupoid also satisfying axiom (2) is known as a Semi-group. Any Semi-group satisfying axiom (4) is a Monoid. Monoid’s also satisfying axiom (5) are known as Groups. Any Group satisfying axiom (3) is an Abelian Group.

public interface IGroupoid<T> {
T Operation(T a, T b);
}

public interface ISemigroup<T> : IGroupoid<T> {

}

public interface IMonoid<T> : ISemigroup<T> {
T Identity { get; }
}

public interface IGroup<T> : IMonoid<T> {
T Inverse(T t);
}

public interface IAbelianGroup<T> : IGroup<T> {

}

Ring Theory

The next logical sub-field of Abstract Algebra to study is Ring Theory which is the study of two operations, $(\cdot)$ and $(+)$, on a single set. In addition to the axioms outlined above, there is an addition axiom for describing how one operations distributes over the other.

1. Distributivity: $a \cdot (b + c) = (a \cdot b) + (a \cdot c), (a + b) \cdot c = (a \cdot c) + (b \cdot c)$

All of the following ring structures satisfy axiom (6). Rings are distinguished by the properties of their operands. The simplest of these structures is the Ringoid where both operands are given by Groupoids. Any Ringoid whose operands are Semi-groups is a Semi-ring. Any Semi-ring whose first operand is a Group is a Ring. Any Ring whose second operand is a Monoid is a Ring with Unity. Any Ring with Unity whose second operand is a Group is Division Ring. Any Division Ring whose operands are both Abelian Groups is a Field.

public interface IRingoid<T, A, M>
where A : IGroupoid<T>
where M : IGroupoid<T> {
M Multiplication { get; }

T Distribute(T a, T b);
}

public interface ISemiring<T, A, M> : IRingoid<T, A, M>
where A : ISemigroup<T>
where M : ISemigroup<T> {

}

public interface IRing<T, A, M> : ISemiring<T, A, M>
where A : IGroup<T>
where M : ISemigroup<T> {

}

public interface IRingWithUnity<T, A, M> : IRing<T, A, M>
where A : IGroup<T>
where M : IMonoid<T> {

}

public interface IDivisionRing<T, A, M> : IRingWithUnity<T, A, M>
where A : IGroup<T>
where M : IGroup<T> {

}

public interface IField<T, A, M> : IDivisionRing<T, A, M>
where A : IAbelianGroup<T>
where M : IAbelianGroup<T> {

}

Module Theory

The last, and more familiar, sub-field of Abstract Algebra is Module Theory which deals with structures with an operation, $(\circ) : S \times R \to R$, over two separate sets: $a,b \in S$ and $x,y \in R$ that satisfy the following axioms.

1. Distributivity of $S$: $a \circ (x + y) = (a \circ x) + (a \circ y)$
2. Distributivity of $R$: $(a + b) \circ x = (a \circ x) + (b \circ x)$
3. Associativity of $S$: $a \circ (b \circ x) = (a \cdot b) \circ x$

All of the following module structures satisfy axioms (7)-(9). A Module consists of a scalar Ring and an vector Abelian Group. Any Module whose Ring is a Ring with Unity is a Unitary Module. Any Unitary Module whose Ring with Unity is a Abelian Group is a Vector Space.

public interface IModule<
TScalar,
TVector,
TScalarRing,
TScalarMultiplicativeSemigroup,
>
where TScalarRing : IRing<TScalar, TScalarAddativeGroup, TScalarMultiplicativeSemigroup>
where TScalarMultiplicativeSemigroup : ISemigroup<TScalar>
{

TScalarRing Scalar { get; }

TVector Distribute(TScalar t, TVector r);
}

public interface IUnitaryModule<
TScalar,
TVector,
TScalarRingWithUnity,
TScalarMultiplicativeMonoid,
>
: IModule<
TScalar,
TVector,
TScalarRingWithUnity,
TScalarMultiplicativeMonoid,
>
where TScalarRingWithUnity : IRingWithUnity<TScalar, TScalarAddativeGroup, TScalarMultiplicativeMonoid>
where TScalarMultiplicativeMonoid : IMonoid<TScalar>
{

}

public interface IVectorSpace<
TScalar,
TVector,
TScalarField,
TScalarMultiplicativeAbelianGroup,
>
: IUnitaryModule<
TScalar,
TVector,
TScalarField,
TScalarMultiplicativeAbelianGroup,
>
where TScalarField : IField<TScalar, TScalarAddativeAbelianGroup, TScalarMultiplicativeAbelianGroup>
where TScalarMultiplicativeAbelianGroup : IAbelianGroup<TScalar>
{

}

Representation of Value Types

The CTS allows for both value and reference types on the .NET Common Language Infrastructure (CLI). The following are examples of how each theory presented above can leverage value types found in the C# language to represent concepts drawn from mathematics.

Enum Value Types and the Dihedral Group $D_8$

One of the simplest finite groups is the Dihedral Group of order eight, $D_{8}$, representing the different orientations of a square, $e$, obtained by reflecting the square about the vertical axis, $b$, and rotating the square by ninety degrees, $a$. The generating set is given by $\lbrace a, b \rbrace$ and gives rise to the set $\lbrace e, a, a^2, a^3, b, ba, ba^2, ba^3 \rbrace$ These elements are assigned names as follows: $\text{Rot}(0) = e$, $\text{Rot}(90) = a$, $\text{Rot}(180) = a^2$, $\text{Rot}(270) = a^3$, $\text{Ref}(\text{Ver}) = b$, $\text{Ref}(\text{Desc}) = ba$, $\text{Ref}(\text{Hoz}) = ba^2$ and $\text{Ref}(\text{Asc}) = ba^3$. The relationship between these elements is visualized below:

The easiest way to represent this group as a value type is with an enum.

enum Symmetry { Rot000, Rot090, Rot180, Rot270, RefVer, RefDes, RefHoz, RefAsc }

From this enum we can define the basic Group Theory algebraic structures to take us to $D_8$.

public class SymmetryGroupoid : IGroupoid<Symmetry> {
public Symmetry Operation(Symmetry a, Symmetry b) {
// 64 cases
}
}

public class SymmetrySemigroup : SymmetryGroupoid, ISemigroup<Symmetry> {

}

public class SymmetryMonoid : SymmetrySemigroup, IMonoid<Symmetry> {
public Symmetry Identity {
get { return Symmetry.Rot000; }
}
}

public class SymmetryGroup : SymmetryMonoid, IGroup<Symmetry> {
public Symmetry Inverse(Symmetry a) {
switch (a) {
case Symmetry.Rot000:
return Symmetry.Rot000;
case Symmetry.Rot090:
return Symmetry.Rot270;
case Symmetry.Rot180:
return Symmetry.Rot270;
case Symmetry.Rot270:
return Symmetry.Rot090;
case Symmetry.RefVer:
return Symmetry.RefVer;
case Symmetry.RefDes:
return Symmetry.RefAsc;
case Symmetry.RefHoz:
return Symmetry.RefHoz;
case Symmetry.RefAsc:
return Symmetry.RefDes;
}

throw new NotImplementedException();
}

}

Integral Value Types and the Commutative Ring with Unity over $\mathbb{Z} / 2^n \mathbb{Z}$

C# exposes a number of fixed bit integral value types that allow a programmer to pick an integral value type suitable for the scenario at hand. Operations over these integral value types form a commutative ring with unity whose set is the congruence class $\mathbb{Z} / 2^n \mathbb{Z} = \lbrace \overline{0}, \overline{1}, \ldots, \overline{2^n-1} \rbrace$ where $n$ is the number of bits used to represent the integer and $\overline{m}$ is the equivalance class $\overline{m} = \lbrace m + k \cdot 2^n\rbrace$ with $k \in \mathbb{Z}$.

Addition is given by $\overline{a} + \overline{b} = \overline{(a + b)}$ just as multiplication is given by $\overline{a} \cdot \overline{b} = \overline{(a \cdot b)}$. Both statements are equivalent to the following congruence statements: $(a + b) \equiv c \pmod{2^n}$ and $(a \cdot b) \equiv c \pmod{2^n}$ respectively.

Under the binary numeral system, modulo $2^n$ is equivalent to ignoring the bits exceeding $n-1$, or equivalently, $\displaystyle \sum_{i = 0}^{\infty} c_i 2^i \equiv \sum_{i = 0}^{n-1} c_i 2^i \pmod{2^n}$ where $c \in \lbrace 0, 1 \rbrace$. As a result only the first (right most) bits need to be considered when computing the sum or product of two congruence classes, or in this case, integer values in C#. Thus, in the following implementation, it is not necessary to write any extra code to represent these operations other than writing them in their native form.

The reason why we are limited to a commutative ring with unity instead of a full field is that multiplicative inverses do not exist for all elements. A multiplicative inverse only exists when $ax \equiv 1 \pmod{2^n}$ where $x$ is the multiplicative inverse of $a$. For a solution to exist, $\gcd(a, 2^n) = 1$. Immediately, any even value of $a$ will not have a multiplicative inverse in $\mathbb{Z} \ 2^n \mathbb{Z}$. However, all odd numbers will.

public class AddativeIntegerGroupoid : IGroupoid<long> {
public long Operation(long a, long b) {
return a + b;
}
}

}

public long Identity {
get { return 0L; }
}
}

public long Inverse(long a) {
return -a;
}
}

}

public class MultiplicativeIntegerGroupoid : IGroupoid<long> {
public long Operation(long a, long b) {
return a * b;
}
}

public class MultiplicativeIntegerSemigroup : MultiplicativeIntegerGroupoid, ISemigroup<long> {

}

public class MultiplicativeIntegerMonoid : MultiplicativeIntegerSemigroup, IMonoid<long> {
public long Identity {
get { return 1L; }
}
}

public class IntegerRingoid : IRingoid<long, AddativeIntegerGroupoid, MultiplicativeIntegerGroupoid> {
public MultiplicativeIntegerGroupoid Multiplication { get; private set;}

public IntegerRingoid() {
Multiplication = new MultiplicativeIntegerGroupoid();
}

public long Distribute(long a, long b) {
return Multiplication.Operation(a, b);
}
}

public class IntegerSemiring : IntegerRingoid, ISemiring<long, AddativeIntegerSemiring, MultiplicativeIntegerSemiring> {
public MultiplicativeIntegerSemiring Multiplication { get; private set;}

public IntegerSemiring() : base() {
Multiplication = new MultiplicativeIntegerSemiring();
}
}

public class IntegerRing : IntegerSemiring, IRing<long, AddativeIntegerGroup, MultiplicativeIntegerSemigroup>{

public IntegerRing() : base() {
}
}

public class IntegerRingWithUnity : IntegerRing, IRingWithUnity<long, AddativeIntegerGroup, MultiplicativeIntegerMonoid> {
public MultiplicativeIntegerMonoid Multiplication { get; private set; }

public IntegerRingWithUnity() : base() {
Multiplication = new MultiplicativeIntegerMonoid();
}
}

Floating-point Value Types and the Real Vector Space $\mathbb{R}^n$

C# offers three types that approximate the set of Reals: floats, doubles and decimals. Floats are the least representative followed by doubles and decimals. These types are obviously not continuous, but the error involved in rounding calculations with respect to the calculations in question are negligible and for most intensive purposes can be treated as continuous.

As in the previous discussion on the integers, additive and multiplicative classes are defined over the algebraic structures defined in the Group and Ring Theory sections presented above. In addition to these implementations, an additional class is defined to describe a vector.

public class Vector<T> {
private T[] vector;

public int Dimension {
get { return vector.Length; }
}

public T this[int n] {
get { return vector[n]; }
set { vector[n] = value; }
}

public Vector() {
vector = new T[2];
}
}

With these classes, it is now possible to implement the algebraic structures presented in the Module Theory section from above.

public class RealVectorModule : IModule<double, Vector<double>, RealRing, AddativeRealGroup, MultiplicativeRealSemigroup, VectorAbelianGroup<double>> {
public RealRing Scalar {
get;
private set;
}

public VectorAbelianGroup<double> Vector {
get;
private set;
}

public RealVectorModule() {
Scalar = new RealRing();
}

public Vector<double> Distribute(double t, Vector<double> r) {
Vector<double> c = new Vector<double>();
for (int i = 0; i < c.Dimension; i++)
c[i] = Scalar.Multiplication.Operation(t, r[i]);
return c;
}
}

public class RealVectorUnitaryModule : RealVectorModule, IUnitaryModule<double, Vector<double>, RealRingWithUnity, AddativeRealGroup, MultiplicativeRealMonoid, VectorAbelianGroup<double>> {
public new RealRingWithUnity Scalar {
get;
private set;
}

public RealVectorUnitaryModule()
: base() {
Scalar = new RealRingWithUnity();
}
}

public class RealVectorVectorSpace : RealVectorUnitaryModule, IVectorSpace<double, Vector<double>, RealField, AddativeRealAbelianGroup, MultiplicativeRealAbelianGroup, VectorAbelianGroup<double>> {
public new RealField Scalar {
get;
private set;
}

public RealVectorVectorSpace()
: base() {
Scalar = new RealField();
}
}

Representation of Reference Types

The following are examples of how each theory presented above can leverage reference types found in the C# language to represent concepts drawn from computer science.

Strings, Computability and Monoids

Strings are the simplest of reference types in C#. From an algebraic structure point of view, the set of possible strings, $\Sigma^{*}$, generated by an alphabet, $\Sigma$, and paired with a concatenation operation, $(+)$, forms a monoid.

public class StringGroupoid : IGroupoid<string> {
public string Operation(String a, String b) {
return string.Format("{0}{1}", a, b);
}
}

public class StringSemigroup : StringGroupoid, ISemigroup<string> {

}

public class StringMonoid : StringSemigroup, IMonoid<string> {
public string Identity {
get { return string.Empty; }
}
}

Monoids over strings have a volley of applications in the theory of computation. Syntactic Monoids describe the smallest set that recognizes a formal language. Trace Monoids describe concurrent programming by allowing different characters of an alphabet to represent different types of locks and synchronization points, while the remaining characters represent processes.

Classes, Interfaces, Type Theory and Semi-rings

Consider the set of types $\mathcal{T}^{*}$, that are primitive and constructed in C#. The generating set of $\mathcal{T}^{*}$ is the set of primitive reference and value types, $\mathcal{T}$, consisting of the types discussed thus far. New types can be defined by defining classes and interfaces.

A simple operation $(\oplus)$ over $\mathcal{T}^{*}$ takes two types, $\alpha, \beta$, and yields a third type, $\gamma$, known as a sum type. In type theory, this means that an instance of $\gamma$ can be either an instance of $\alpha$ or $\beta$. A second operation $(\otimes)$ over $\mathcal{T}^{*}$ takes two types and yields a third type representing a tuple of the first two types. In other words, $\alpha \otimes \beta = (\alpha, \beta)$.

Both operations form a semi-group $(\mathcal{T}^{*}, \otimes)$ and $(\mathcal{T}^{*}, \otimes)$ and in conjunction the two form a semi-ring.

To implement this semi-ring is a little involved. The .NET library supports emitting dynamic type definitions at runtime. For sum types, this would lead to an inheritance view of the operation. Types $\alpha$ and $\beta$ would end up deriving from $\gamma$ which means that any sequence of sums would yield an inheritance tree. A product type would result in composition of types with projection operations, $\pi_{n} : \prod_{i = 0} \tau_{i} \to \tau_{n}$, to access and assign the $n\text{'th}$ element of the composite. Both type operation implementations are outside the scope of this write-up and I’ll likely revisit this topic in a future write-up.

Delegates and Process Algebras

The third type of reference type to mention is the delegate type which is C#’s approach to creating first-class functions. The simplest of delegates is the built-in Action delegate which represents a single procedure taking no inputs and returning no value.

Given actions $a, b \in \mathcal{A}$, we can define a possible execution operator, $(\Vert) : \mathcal{A} \times \mathcal{A} \to \mathcal {A}$, where either $a$ or $b$ is executed denoted as $a \lVert b$. The choice operation forms a commutative semigroup $(\mathcal{A}, \lVert)$ since operations are associative $a \lVert (b \lVert c) = (a \lVert b) \lVert c$ and the operation is commutative $a \lVert b = b \lVert a$.

A product operation, $(\to) : \mathcal{A} \times \mathcal{A} \to \mathcal {A}$, representing the sequential execution of $a$ and then $b$ is given by $a \to b$. The sequence operator forms a groupoid with unity since the operation is not associative $a \to (b \to c) \neq (a \to b) \to c$ and there is an identity action $e$ representing a void operation resulting in $e \to a = a$.

Both operations together form a ringoid, $(\mathcal{A}, \to, \lVert)$ since the sequence operation distributes over the choice operation $a \to (b \lVert c) = (a \to b) \lVert (a \to c)$. Meaning that $a$ takes place and then $b$ or $c$ takes places is equivalent to $a$ and then $b$ takes place or $a$ and then $c$ takes place.

public class SequenceGroupoidWithUnity<Action> : IGroupoid<Action> {
public Action Identity {
get { return () => {}; }
}

public Action Operation(Action a, Action b) {
return () => { a(); b(); }
}
}

public class ChoiceGroupoid<Action> : IGroupoid<Action> {
public Action Operation(Action a, Action b) {
if(DateTime.Now.Ticks % 2 == 0)
return a;
return b;
}
}

The process algebra an be extended further to describe parallel computations with an additional operation. The operations given thus far enable one to derive the possible execution paths in a process. This enables one to comprehensively test each execution path to achieve complete test coverage.

Examples

The motivation of this work was to achieve C++’s approach to parametric polymorphism by utilizing C# subtype polymorphism to define the algebraic structure required by a method (akin to the built-in operations on types in C++). To illustrate how these interfaces are to be used, the following example extension methods operate over a collection of a given type and accept the minimal algebraic structure to complete the computation. The result is a single implementation of the calculation that one would expect in C++.

static public class GroupExtensions {
static public T Sum<T>(this IEnumerable<T> E, IMonoid<T> m) {
return E
.FoldL(m.Identity, m.Operation);
}
}

static public class RingoidExtensions {
static public T Count<T, R, A, M>(this IEnumerable<R> E, IRingWithUnity<T, A, M> r)
where A : IGroup<T>
where M : IMonoid<T> {

return E
.Map((x) => r.Multiplication.Identity)
}

static public T Mean<T, A, M>(this IEnumerable<T> E, IDivisionRing<T, A, M> r)
where A : IGroup<T>
where M : IGroup<T> {

return r.Multiplication.Operation(
r.Multiplication.Inverse(
E.Count(r)
),
);
}

static public T Variance<T, A, M>(this IEnumerable<T> E, IDivisionRing<T, A, M> r)
where A : IGroup<T>
where M : IGroup<T> {

T average = E.Mean(r);

return r.Multiplication.Operation(
r.Multiplication.Inverse(
E.Count(r)
),
E
.Map((x) => r.Multiplication.Operation(x, x) )
);
}
}

static public class ModuleExtensions {
static public TV Mean<TS, TV, TSR, TSRA, TSRM, TVA>(this IEnumerable<TV> E, IVectorField<TS, TV, TSR, TSRA, TSRM, TVA> m)
where TSR : IField<TS, TSRA, TSRM>
where TSRA : IAbelianGroup<TS>
where TSRM : IAbelianGroup<TS>
where TVA : IAbelianGroup<TV> {

return m.Distribute(
m.Scalar.Multiplication.Inverse(
E.Count(m.Scalar)
),
E.FoldL(
m.Vector.Identity,
m.Vector.Operation
)
);
}
}

Conclusion

Abstract Algebra comes with a rich history and theory for dealing with different algebraic structures that are easily represented and used in the C# language to perform type agnostic computations. Several examples drawn from mathematics and computer science illustrated how the solution can be used for both value and reference types in C# and be leveraged in the context of a few example type agnostic computations. The main benefit of this approach is that it minimizes the repetitious coding of computations required under the ad-hoc polymorphism approach adopted by the designers of C# language. The downside is that several structures must be defined for the types being computed over and working with C# parameter constraint system can be unwieldy. While an interesting study, this solution would not be practical in a production setting under the current capabilities of the C# language.

References

Baeten, J.C.M. A Brief History of Process Algebra [pdf]. Department of Computer Science, Technische Universiteit Eindhoven. 31 Mar. 2012.

ECMA International. Standard ECMA-335 Common Language Infrastructure [pdf]. 2006.

Fokkink, Wan. Introduction of Process Algebra [pdf]. 2nd ed. Berlin: Springer-Verlang, 2007. 10 Apr. 2007. 31 Mar. 2012.

Goodman, Joshua. Semiring Parsing [pdf]. Computational Linguistics 25 (1999): 573-605. Microsoft Research. 31 Mar. 2012.

Hungerford, Thomas. Algebra. New York: Holt, Rinehart and Winston, 1974.

Ireland, Kenneth. A classical introduction to modern number theory. New York: Springer-Verlag, 1990.

Litvinov, G. I., V. P. Maslov, and A. YA Rodionov. Universal Algorithms, Mathematics of Semirings and Parallel Computations [pdf]. Spring Lectures Notes in Computational Science and Engineering. 7 May 2010. 31 Mar. 2012 .

Mazurkiewicz, Antoni. Introduction to Trace Theory [pdf]. Rep. 19 Nov. 1996. Institute of Computer Science, Polish Academy of Sciences. 31 Mar. 2012.

Pierce, Benjamin. Types and programming languages. Cambridge, Mass: MIT Press, 2002.