# Category Theory for Programmer
[toc]
- Lecture Videos (Youtube): https://www.youtube.com/watch?v=I8LbkfSSR58&list=PLbgaMIhjbmEnaH_LTkxLI7FMa2HsnawM_
- Github repo: https://github.com/leonw774/Category-Theory-for-Programmer
- This note on Hackmd: https://hackmd.io/OkQvCLkHSmaizwJGSJCUUA
# 1. What is category
CATEGORY = (set of objects, set of morphisms)
EMPTY CATEGORY = (Empty set, empty set)
## Objects & Morphisms
In categort theory, objects is any thing in the objects set. Like nodes & arrows in graph, a morphism is a relationship from an object to an other object.
Objects in a category must have an **identity morphism**, an arrow started from itself toward itself.
Morphisms in category must be associative:
- associative: $f, g, h$ are morphisms, $f \circ (g \circ h) = (f \circ g) \circ h$
## Some special morphisms
A morphism $f: x → y$ is **monomorphisms**
- if $f \circ g1 = f \circ g2$ implies $g1 = g2$ for all morphisms $g1, g2: z → x$.
- Monomorphisms has a **left inverse** if there is a morphism $g: y → x$ such that $g \circ f = id_x$.
A morphism $f: x → y$ is **epimorphism**
- if $g1 \circ f = g2 \circ f$ implies $g1 = g2$ for all morphisms $g1, g2: y → z$.
- Epimorphism has a **right inverse** if there is a morphism $g: y → x$ such that $f \circ g = id_y$.
A morphism $f: x → y$ is **isomorphism**
- if there exists a morphism $g: y → x$ such that $f \circ g = id_y$ and $g \circ f = id_x$.
- $g$ is called simply the **inverse** of $f$.
## Pure vs. Dirty Function
In programming, a pure function is a function that is stable from its input types to output types, it can not be effected by anything out of its scope and have no side effect outside its scope.
# 2. Some Categories
## Free Categories
A category is free category iff $∀$ morphisms $f, g$ , $(f \circ g)$ is also in its morphisms. Example: `funciton-composition.cpp`
## Monoid
Monoid is a kind of group that is simplest category
Monoid $(S, \cdot)$ has:
- A binary operation \dot
+ closed
+ associative
- The identity
The group $(Z^+_N, +)$ is a monoid where $Z^+_N$ is set of positive finite integers smaller than $N$, $N$ is a prime number, and $+$ is a binary operation of addition modulo N.
To see monoid as category:
- The singleton object $S$
- The morphisms are $a$, where $a \in S$
- The composition of morphisms: $a \cdot b$
- The identity morphism is the identity element
# 3. Kleisli Categories
Consider following function:
``` C++
std::string logger;
bool neg(bool b) {
logger += "Neg!";
return !b;
}
```
This function is not a pure function as it is not stable AND have side effect. To rewrite it as a pure function, we let the output type of function be an *embellished type* made of `bool b` and `string logger`:
``` C++
typedef pair<bool, string> bool_writer;
bool_writer neg(bool n) {
return bool_writer(!n.first, "Neg!");
}
```
## Definition
We want to define a function composition operater that *chain up* our log strings together. There is a category out of it: Kleisli category:
- Let $m$ be a morphism that make the input/output of a function into *embellished type*.
- $m$ is a part of so-called *monad*. (Detailed in latter chapter.)
- Let f, g be morphisms in Kleisli Category:
- $f: t_1 → m(t_2)$
- $g: t_2 → m(t_3)$
- The composition under Kleisli is $(g \circ f): t_1 → m(t_3)$
## Comparison table
Let's say there is a category $C$, object $x, y, z$ and two morphisms $f: x → y$ and $g: y → z$.
Their Kleisli counterpart over morphism $m$ are category $C_k$, object $x', y', z'$ and two morphisms $f': x' → y'$ and $g': y' → z'$.
| $C$ | $C_k$ |
| --- | --- |
| $m$ | $id_{X'}$ |
| $m \circ f$ | $f'$ |
| $m \circ g$ | $g'$ |
| $m \circ g \circ f$| $g' \circ f'$ |
### Example Code
`kleisli.cpp`
# 4. Initial, Terminal Object & Duality
## Initial and Terminal Object
- An object $i$ is a inital object in $C$ if for every object $x$ in $ob(C)$, there exists exactly one morphism $i → x$.
- An object $t$ is a terminal object in $C$ if for every object $x$ in $ob(C)$, there exists exactly one morphism $x → t$.
Initial & terminal objects are *unique up to unique isomorphism*.
### Example
- The empty set is the unique initial object in *the category of sets*. Because there is exactly one empty function for each set, thus the empty function $\varnothing → x$ is not equal to $\varnothing → y$ if and only if $x \ne y$
- Every one-element set (singleton) is a *terminal object* in *the category of sets*.
## Opposite Category
$C_{op}$ of a given category $C$ is formed by reversing the morphisms, i.e. interchanging the source and target of each morphism.
## Duality
Given a statement regarding the category $C$, by interchanging the source and target of each morphism as well as interchanging the order of composing two morphisms, a corresponding dual statement is obtained regarding the opposite category $C_{op}$. In other words, if a statement is true about $C$, then its dual statement is true about $C_{op}$.
Applying duality, this means an *initial object* in a category $C$ is a *terminal object* the category $C_{op}$
# 5. Product & Coproudct
## Definition
A **product** of object $a$ and $b$ is a object $c$ equipped with two morphisms $p_1: c → a$ and $p_2: c → b$ satisfying the following universal property:
- For every object $d$ equipped with any pair of morphisms $q_1: d → a$ and $q_2: d → b$ there is a *unique* morphism $m: d → c$ such that $q_1 = p_1 \circ m$ and $q_2 = p_2 \circ m$. (We say that $m$ factorize $q_1$ and $q_2$)
```
D (any product candidate)
| | |
(q1)| (exist unique m) |(q2)
v v v
A <-p1- C -p2-> B
```
```mermaid
graph TD
D -->|q1| A
C -->|p1| A
C -->|p2| B
D -->|q2| B
D -.->|!m| C
```
We denote the product of $a$ and $b$ as $a \times b$
Whether a product exists may depend on the category or on $a$ and $b$. If it does exist, *it is unique up to canonical isomorphism*, so one may speak of *the* product.
The product is the terminal object in the category of product candidates.
The morphisms $p_1$ and $p_2$ are called the **canonical projections** or **projection morphisms**.
## Examples of Product
The classic example of product is a **pair**. But to make a clearer example, let's say we want a class for the product of `int` and `bool`. This class `Product` will have two method:
``` C++
class Product {
int toInt(); // Product -> int
bool toBool(); // Product -> bool
}
```
How do we know whether a implementation of `Product` is the product of not?
``` C++
class BadProduct {
int data;
int toInt() { return data; }
bool toBool() { return true; }
}
class CounterBadProduct {
int data1
bool data2;
int toInt() { return data1; }
bool toBool() { return data2; }
BadProduct toBadProduct(); // does not exist
}
```
Here, `BadProduct` is not a product of `int` and `bool`, because there is a `CounterBadProduct`. We expect for every instance `p` of `CounterBadProduct`, it holds that `p.toBool() == p.toBadProduct().toBool()`. But apparently it is not the case if `p.toBool() == false`.
``` C++
class BadProduct2 {
int data1;
int data2;
bool data3;
BadProduct2(int d1, int d2, bool d3) :
data1(d1), data2(d2), data3(d3) {};
int toInt() { return data1; }
bool toBool() { return data3; }
}
class CounterBadProduct2 {
int data1;
bool data2;
int toInt() { return data1; }
bool toBool() { return data2; }
BadProduct2 toBadProduct2() { // is not unique
return BadProduct2(data1, /* can be any integer */, data2);
};
}
```
In this case, there is `toBadProduct2()` such that, for every instance `p` of `CounterBadProduct2`, it holds that `p.toBool() == p.toBadProduct2().toBool()` and `p.toInt() == p.toBadProduct2().toInt()`. However, `toBadProduct2()` is not unique. So `BadProduct2` is not the product of `int` and `bool`.
``` C++
class PossibleProduct {
int data1;
int toInt() { return data1 / 2; }
bool toBool() { return data % 2 > 0; }
}
class CounterPossibleProduct {
int data1;
bool data2;
int toInt() { return data1; }
bool toBool() { return data2; }
BadProduct CounterPossibleProduct(); // could exist if int is a infinite set
}
```
If cardinality of `int` is infinite, then this `PossibleProduct` is a product of `int` and `bool`. However, in C++, cardinality of `int` is finite. So it can't be the product in this programming language, considering an instance `p` of `CounterPossibleProduct` where `p.toInt() == pow(2, 62)`.
## What is "unique up to isomorphism"
Unique up to isomorphism means that all the objects satisfying a given definition are isomorphic -- there must exist an isomorphism between those objects.
if every isomorphism between those objects are unique, it is called **"unique up to *unique* isomorphism"**
### Why are they "unique"?
All isomorphic objects have the same relationship with other objects.
Suppose $a \simeq b$ with isomorphism $α: a → b$, then for all object $y$, there are $Hom(a, y)$ and $Hom(b, y)$ and a bijection function $ϕ: Hom(a, y) → Hom(b, y)$.
$Hom(a, y)$ means the set of all morphism from $A$ to $Y$
Because for all $f: a → y$ and $g: b → y$:
$$
\begin{aligned}
ϕ(f) &= g = f \circ α^{-1} \\
ϕ^{-1}(g) &= f = g \circ α \\
\end{aligned}
$$
we show that $ϕ^{-1} \circ ϕ: Hom(a, y) → Hom(a, y)$ is the identity function:
$$
\begin{aligned}
(ϕ^{-1} \circ ϕ)(f) &= ϕ^{-1}(ϕ(f)) \\
&= ϕ^{-1}(f \circ α^{-1}) \\
&= f \circ α^{-1} \circ α \\
&= f \circ id_a \\
&= f \\
\end{aligned}
$$
Strictly speaking we should also show that $ϕ \circ ϕ^{−1}$ is also the identity function, but the proof is almost identical so we’ll omit it.
So we have shown that **there is a bijection between sets of morphisms of isomorphic objects**, and therefore they are formally of the same shape.
## Coproduct: Dual of Product
Coproduct is basically the product with reverse direction of morphisms in the definition.
Let object $C$ be the product of object $A$ and $B$, the coproduct of $C$ is $C_{op}$ equipped with two morphisms $p_1: a → c$ and $p_2: b → c$ satisfying the following universal property:
- For every object $d$ and every pair of morphisms $q_1: a → d$ and $q_2: b → d$ there is a *unique* morphism $m: c → d$ such that $q_1 = (m \circ p_1)$ and $q_2 = (m \circ p_2)$. (We say that $m$ factorize $q_1$ and $q_2$)
We denote the product of $A$ and $B$ as $A \oplus B$
The programming counterpart of a coproduct is a **tagged-union**. Consider a structure `Either` that can be either a type A or type B but not both. We can implement it in this way:
``` C++
// The object Either is the coproduct of type A and type B
template <typename A, typename B>
class Either {
private:
enum {isLeft, isRight} tag;
A left;
B right;
public:
// mapping from A -> Either
Either set(A a) {
this.tag = isLeft;
left = a;
}
// mapping from B -> Either
Either set(B a) {
this.tag = isRight;
right = b;
}
// return defaultValue if Either is not type A currently
A getLeft(A defaultVal) {
return (this.tag == isLeft) ? this.left : defaultVal;
}
// return defaultValue if Either is not type B currently
B getRight(B defaultVal) {
return (this.tag == isRight) ? this.right : defaultVal;
}
}
```
## Product and Coproduct as Algebra
A category and product/coproduct forms symmetric monoid.
Commutativity: it is pretty obvious that there existed a unique isomorphism from $a \times b$ to $b \times a$, which is $Swap()$.
Associativity: we can find an isomorphism from $a \times (b \times c)$ to $(a \times b) \times c$. To show that, consider the functions that turns $(a, (b, c))$ into $((a, b), c)$. Those functions exists and all have inverses.
Identity:
- For product, it is the terminal object $t$.
- For coproduct, it is the initial object $i$.
Let $b = a \times t$, $p: b → a$, and $u_b: b → t$. Because the uniqueness of ternimal morphisms $u_b$ is unique.
Considering $a$ itself as the product candidate with $id_a: a → a$ and $u_a: a → t$. We then have an unique morphism $m: a → b$. By the universal property of the product, $id_a = p \circ m$ and $u_a = u_b \circ m$.
Consider:
$$
\begin{aligned}
u_a &= u_b \circ m \\
u_a \circ p &= u_b \circ m \circ p \\
\end{aligned}
$$
since $u_a \circ p$ is morphism from $b → t$, it is unique, therefore $u_a \circ p = u_b$. We have:
$$
u_b = u_b \circ m \circ p \\
$$
By uniqueness of identity, $m \circ p = id_b$
Since $p \circ m = id_a$ and $m \circ p = id_b$. $p$ is an isomorphism. Thus $b \simeq a$.
In similar way, we can prove the identityness of initial object $I$ in coproduct operation.
Product is **distributive** with respect to coproduct. It makes the category equipped with product and coproduct a **semi-ring**.
In C++17 `std::optional<T>` is the coproduct of a initial object `nullopt_t` and the type `T`. We can write it in algebra as $optional(T) = 1 \oplus T$
In Haskell, the `List` type is recursively defined as:
```Haskell
data List x = Empty | x : (List x)
```
We can write down the difinition in algebra:
$$
\begin{aligned}
List(x) &= 1 + x \times List(x) \\
List(x) &= 1 + x \times (1 + x \times List(x)) \\
List(x) &= 1 + x \times 1 + x \times x \times List(x) \\
\vdots \\
List(x) &= 1 + x + x^2 + x^3 \cdots
\end{aligned}
$$
# 6. Functors
**Functor** is the mapping from category to category.
To map two categorys $C$ and $D$:
- Map the set of objects in $C$ to the set of objects in $D$
- Map the set of morphisms from $C$ to the set of morphisms in $D$, in the way that **structure is preserved**
For a functor $F: C → D$, for all $f: x → y$ and $g: y → z$ in $C$, it holds that:
1. $F(f): F(x) → F(y)$
2. $F(g \circ f): F(x) → F(z) = F(g) \circ F(f)$
3. $F(id_x) = id_{F(x)}$
```mermaid
flowchart TB
subgraph domainCategory
x -- f --> y
y -- g --> z
x -- "g∘f" --> z
end
subgraph targetCategory
Fx["F(x)"] -- "F(f)" --> Fy["F(y)"]
Fy -- "F(g)" --> Fz["F(z)"]
Fx -- "F(g∘f)" --> Fz
end
x -.-> Fx
y -.-> Fy
z -.-> Fz
```
**Functors are morphisms.** They can do compositions too. For categories $C, D, E$, if there are functor $F: C → D$ and $G: D → E$, we can have composed functor $H = G \circ F: C → E$.
Functors and categories make a **category of categories**.
## Special Functors
- **Faithful functor** is injective on the morphism sets
- **Full functor** is surjective on the morphism sets.
- **Constant functor** at $x$, denoted as $\Delta_X: C → D$, maps all objects from category $C$ to a fixed object $x \in D$ and maps all the morphisms to the identity morphism $id_x$.
- **Endofunctor** maps a category to itself.
- **Identity functor** maps category to itself and nothing is changed.
- **Contravariant functor** $F: C → D$ satisfies $F(g \circ f) = F(f) \circ F(g)$, in other words it, the second condition is reversed. We can write a contravariant functor in normal/covariant functor as $F: C_{op} → D$
- **Presheaf** on a category $C$ is a functor $F: C_{op} → \mathbf{Set}$
## Examples of functor in programming
### Function Mapper
Let say we have some functions that input and output numbers (`int`, `float`, `double`, etc.). But at certain point, we want these functions to work on numbers encoded as `string`. In this case, a functor to wrap the functions is useful. See example code: `functor.cpp`
### Safe `list::pop_front`
A standard C++ `list::pop_front` function has undefined behavior when the input list is empty. It is good that we return a `optional<list<T>>` object for safety. However, we don't want to re-write the functions working on `list` for `optional<list>`. We can simply write the functor `optionalized()` to transform the original function from category of `T` to the category of `optional<T>`.
``` C++
template <typename T>
std::optional<std::list<T>> safe_get_tail(std::list<T> list) {
if (list.size() == 0)
return std::nullopt;
std::list<T> resultList(list);
resultList.pop_front();
return resultList;
}
template <typename T, typename U>
auto optionalized(T (*func)(U)) {
return [func] (std::optional<U> input) {
if (input)
return func(input);
return std::nullopt;
}
}
double square_map(list<double> list) {
std::list<double> resultList;
std::transform(list.begin(),list.end(),resultList.begin(),
[](double x) {return x * x;}
);
return resultList;
}
int main() {
std::list<double> goodList = {1, 3, 5, 7, 9};
std::list<double> emptyList = {};
auto goodTail = safe_get_tail(goodList);
auto emptyTail = safe_get_tail(emptyList);
auto goodSquaredTail optionalized(square_map)(goodListTail);
auto emptySquardTail optionalized(square_map)(emptyTail);
return 0;
}
```
# 7.1 Bifunctors
A **bifunctor** is a functor
- from a **product category**
- to a category
### Product Category
Let there be two category $C, D$, the product of $C$ and $D$ contains all possible the pairs of objects and morphisms between $C$ and $D$, that is
- $ob(C \times D) = \{ (x, y) \mid x \in ob(C), y \in ob(D) \}$
- $mor(C \times D) = \{ (f, g) \mid f \in mor(C), g \in mor(D) \}$
The morphism composition works like vectors.
So a bifunctor takes the form of $F: C \times D → E$.
### Difinition
A bifunctor $F: C \times D → E$, for all morphism $(f_1, f_2): (x_1 → y_1, x_2 → y_2)$ and $(g_1, g_2): (y_1 → z_1, y_2 → z_2)$, it holds that
- $F(f_1, f_2): F(x_1, x_2) → F(y_1, y_2)$
- $F((g_1, g_2) \circ (f_1, f_2)): F(x_1, x_2) → F(z_1, z_2) = F(g_1, g_2) \circ F(f_1, f_2)$
- $F(id_{x_1}, id_{x_2}) = id_{F(x_1, x_2)}$
### Pair of Objects and Morphisms
The product or coproduct of every pair of object do not necessary exists. But if they do, we can construct a new category $C'$ that
- $ob(C') = \{ x \times y \mid x \in ob(C), y \in ob(D) \}$
- $mor(C') = \{ f \times g \mid x \in mor(C), y \in mor(D) \}$
Same with coproduct:
- $ob(C'_{op}) = \{ x \oplus y \mid x \in ob(C), y \in ob(D) \}$
- $mor(C'_{op}) = \{ f \oplus g \mid x \in mor(C), y \in mor(D) \}$
And there are obvious bifunctors $F': (C \times D) → C'$ and $F'_{op}: (C \times D) → C'_{op}$
### Wait... What is the Product and Coproduct of Morphism?
The product of morphisms $f: a → a'$ and $g: b → b'$, denoted by $f \times g$, is the unique morphism that
$$
f \times g: (a \times b) → (a' \times b')
$$
$f \times f'$ is unique because
- As the definition of product of objects says, object $(a \times b)$ has morphism $p: (a \times b) → A$ and $q: (a \times b) → b$
- We have composite morphisms $f \circ p: (a \times b) → a'$ and $g \circ q: (a \times b) → b'$
- The definition of product of objects also says that, for any object that has morphism to $a'$ and $b'$, there exists an unique morphism from that object to $a' \times b'$
- So, $(a \times b) → (a' \times b')$ is unique.
Similar proof goes with coproduct.
### Examples of Bifunctor in Programming
In Haskell, bifunctor looks like
``` haskell
class Bifunctor f where
bimap (a -> a') -> (b -> b') -> (f a b -> f a' b')
```
The `Either` class, defined earlier in C++ to explain coproduct, is a bifunctor if it can be applied on functions
``` C++
// The object Either is the coproduct of type A and type B
template <typename A, typename B>
class Either {
/* ... */
// map two functions into one
template <typename P, typename Q>
auto either_do(
P (*funcL)(A),
Q (*funcR)(B)
) {
return [funcL, funcR] (Either<A, B> input, A lDefault, B rDefault) {
Either<P, Q> result;
if (input.tag == isLeft)
result.set(
funcL(input.getLeft(lDefault))
);
else
result.set(
funcR(input.getRight(rDefault))
);
return result;
}
}
}
```
This bifunctor `Either`, maps function pair `P funcL(A)` and `Q funcR(B)` into `Either<P, Q> ()(Either<A, B>)`.
# 7.2 Monoidal Categories
A monoidal category $(C, \otimes, I)$ is consisting of
- A category $C$.
- A bifunctor $\otimes: C \times C → C$. (binary operation)
- The $\otimes$ is called **tensor product**
- $\otimes$ is *associative (up to natural isomorphism)*
- An unit object $e \in ob(C)$ such that $\forall a \in ob(C), (\otimes(e, a) \simeq a \land \otimes(a, e) \simeq a)$. (identity element)
- Three natural isomorphisms are involved
- The associator
- $α_{a,b,c}: \otimes(\otimes(a, b), c) → \otimes(a, \otimes(b, c))$
- The left and right unitor
- $λ_a: \otimes(e, a) → a$
- $ρ_a: \otimes(a, e) → a$
We will write $\otimes(a, b)$ as $a \otimes b$ from now on.
## Example of monoidal categories
Let make a simple monoidal category from the category of C++ `string`. It has a simple bifunctor `concat`. The application of `concat` is to concatenate two strings. The unit object in this monoidal category should be a instance of string `e`, such that `Concat(e, x) == Concat(x, e)`. This instance of string is obviously an empty string. The implementation is in file `monoidalCategory.cpp`.
Note that, in the monoidal category of string concatenation, the morphism between strings are *permutations*.
# 7.3 Profunctor