In my last post about category theory, I introduced the basic idea of typeclasses, and showed how to implement an algebraic monoid as a typeclass. Then we used that algebraic monoid as an example of how to think with arrows, and built it up into a sketch of category theory’s definition of a monoid. We ended with an ominous looking diagram that illustrated what a categorical monoid looks like.
In this post, we’re going to take a look at some of the formal definitions of the basic ideas of category theory. By the end of this lesson, you should be able to look at the categorical monoid, and understand what it means. But the focus of this post will be on understanding initial and terminal objects, and the role they play in defining abstractions in category theory. And in the next post, we’ll see how abstractions compose, which is where the value of category theory to programmers will really become apparrent.
Before I really get started: there’s a lot of terminology in category theory, and this post has a lot of definitions. Don’t worry: you don’t need to remember it all. You do need to understand the concepts behind it, but specifically remembering the difference between, say, and endomorphism, and epimorphism, and a monomorphism isn’t important: you can always look it up. (And I’ll put together a reference glossary to help make that easy.)
Defining Categories
A category is basically a directed graph – a bunch of dots connected by arrows, where the arrows have to satisfy a bunch of rules.
Formally, we can say that a category consists of a the parts: , where:
- is a collection of objects. We don’t actually care what the objects are – the only thing we can do in category theory is look at how the objects related through arrows that connect them. For a category , we’ll often call this collection Obj(C).
- is a collection of arrows, often called morphisms. Each element of starts at one object called its domain (often abbreviated dom), and ending at another object called its codomain (abbreviated cod). For an arrow that goes from to , we’ll often write it as . For a category , we’ll often call this set Mor(C) (for morphisms of C).
- is a composition operator. For every pair of arrows , and , there must be an arrow called the compositions of and .
- To be a category, these must satisfy the following rules:
- Identity: For every object , there must be an arrow from to , called the identity of . We’ll often write it as . For any arrow ; and for any arrow . That’s just a formal way of saying that composing an identity arrow with any other arrow results in the the other arrow.
- Associativity: For any set of arrows .
When talking about category theory, people often say that an arrow is a structure preserving mapping between objects. We’ll see what that means in slightly more detail with some examples.
A thing that I keep getting confused by involves ordering. Let’s look at a quick little diagram for a moment. The path from X to Z is – because comes after , which (at least to me) looks backwards. When you write it in terms of function application, it’s . You can read as g after f, because the arrow comes after the arrow in the diagram; and if you think of arrows as functions, then it’s the order of function application.
Example: The category Set
The most familiar example of a category (and one which is pretty canonical in category theory texts) is the category Set, where the objects are sets, the arrows between them are total functions, and the composition operator is function composition.
That might seem pretty simple, but there’s an interesting wrinkle to Set.
Suppose, for example, that we look at the function . That’s obviously a function from to Int to Int. Since Int is a set, it’s also an object in the category Set, and so is obviously an arrow from . .But there’s also a the set Int+, which represents the set of non-negative real numbers. is also a function from Int+ to Int+. So which arrow represents the function?
The answer is both – and many more. (It’s also a function from the reals to complex numbers, because every real number is also a complex number.) And so on. A function isn’t quite an arrow: an arrow is a categorical concept of some kind of mapping between two objects. In many ways, you can think of an arrow as something almost like a function with an associated type declaration: you can write many type declarations for a given function; any valid function with a type declaration that is an arrow in Set.
We’ll be looking at Set a lot. It’s a category where we have a lot of intuition, so using it as an example to demonstrate category concepts will be useful.
Example: The category Poset
Poset is the category of all partially ordered sets. The arrows between objects in posets are order-preserving functions between partially ordered sets. This category is an example of what we mean by structure-preserving mappings: the composition operator must preserve the ordering property.
For that to make sense, we need to remember what partially ordered set is, and what it means to be an order preserving function.
- A set is partially ordered if it has a partial less-than-or-equal relation, . This relation doesn’t need to be total – some values are less than or equal to other values; and some values can’t be compared.
- A function between two partially ordered sets is order-preserving if and only if for all values , if in , then in .
The key feature of an object in Poset is that is possesses a partial ordering. So arrows in the category must preserve that ordering: if is less than , then must be less than .
That’s a typical example of what we mean by arrows as structure preserving: the objects of a category have some underlying structural property – and to be an arrow in the category, that structure must be preserved across arrows and arrow composition.
Commuting Diagrams
One of the main terms that you’ll hear about category diagrams is about whether or not the diagram commutes. This, in turn, is based on arrow chasing.
An arrow chase is a path through the diagram formed by chaining arrows together by composing them – an arrow chase is basically discovering an arrow from one object to another by looking at the composition of other arrows in the category.
We say that a diagram commutes if, for any two objects and in the diagram, every pair of paths between and compose to the same arrow. Another way of saying that is that if is the set of all paths in the diagram between and , .
For example: In this diagram, we can see two paths: and . If this diagram commutes, it means that following from to and from to must be the same thing as following from to and from to . It doesn’t say that and are the same thing – an arrow chase doesn’t tell us anything about single arrows; it just tells us about how they compose. So what we know if this diagram commutes is that .
Diagrams and Meta-level reasoning: an example
Let’s look at a pretty tricky example. We’ll take our time, because this is subtle, but it’s also pretty typical of how we do things in category theory. One of the key concepts of category theory is building a category, and then using the arrows in that category, create a new category that allows us to do meta-level reasoning.
We’ve seen that there’s a category of sets, called Set.
We can construct a category based on the arrows of Set, called Set→. In this category, each of the arrows in Set is an object. So, more formally, if then .
The arrows of this new category are where it gets tricky. Suppose we have two arrows in Set, and . These arrows are objects in } There is an arrow from to in if there is a pair of arrows and in such that the following diagram commutes:
The diagram is relatively easy to read and understand; explaining it in works is more complicated:
- an arrow in our category of Set-arrows is a mapping from one Set-arrow to another Set-arrow .
- That mapping exists when there are two arrows and in where:
- is an arrow from the domain of to the domain of ;
- is an arrow from the codomain of to the codomain of ; and
- .
Another way of saying that is that there’s an arrow means that there’s a structure-preserving way of transforming any arrow from into an arrow from .
Why should we care about that? Well, for now, it’s just a way of demonstrating that a diagram can be a lot easier to read than a wall of text. But this kind of categorical mapping will become important later.
Categorizing Things
As I said earlier, category theory tends to have a lot of jargon. Everything we do in category theory involves reasoning about arrows, so there are many terms that describe arrows with particular properties. We’ll look at the most basic categories now, and we’ll encounter more in later lessons.
Monics, Epics, and Isos
The easiest way to think about all of these categories is by analogy with functions in traditional set-based mathematics. Functions and their properties are really important, so we define special kinds of functions with interesting categories. We have injections (functions from A to B where every element of A is mapped onto a unique element of B), surjections (functions from A to B where each element of B is mapped onto by an element of A), and isomorphisms.
In categories, we define similar categories: monomorphisms (monics), epimorphisms (epics), and isomorphisms (isos).
- An arrow in category C is monic if for any pair of arrows and in C, implies that . (So a monic arrow discriminates arrows to its domain – every arrow to its domain from a given source will be mapped to a different codomain when left-composed with the monic.)
- An epic is almost the same, except that it discriminates with right-composition: An arrow in category C is epic if for any pair of arrows and in C, implies that . (So in the same way that a monic arrow discriminations arrows to its domain, an epic arrow discriminates arrows from its codomain.)
These definitions sound really confusing. But if you think back to sets, you can twist them into making sense. A monic arrow describes an injection in set theory: that is, a function maps every element of onto a unique element of . So if you have some functions and that maps from some set onto , then the only way that can map onto in the same way as is if and map onto in exactly the same way.
The same basic argument (reversed a bit) can show that an epic arrow is a surjective function in Set.
- An isomorphism is a pair of arrows and where is monic and is epic, and where , and .
We say that the objects and are isomorphic if there’s an isomorphism between them.
Initial and Terminal Objects
Another kind of categorization that we look at is talking about special objects in the category. Categorical thinking is all about arrows – so even when we’re looking at special objects, what make them special are the arrows that they’re related to.
An initial object 0 in a category is an object where for every object , there’s exactly one arrow . Similarly, a terminal object in a category is an object where for every object , there is exactly one arrow .
For example, in the category Set, the empty set is an initial object, and singleton sets are terminal objects.
A brief interlude:
What’s the point?In this lesson, we’ve spent a lot of time on formalisms and definitions of abstract concepts: isos, monos, epics, terminals. And after this pause, we’re going to spend a bunch of time on building some complicated constructions using arrows. What’s the point of all of this? What does any of these mean?
Underlying all of these abstractions, category theory is really about thinking in arrows. It’s about building structures with arrows. Those arrows can represent import properties of the objects that they connect, but they do it in a way that allows us to understand them solely in terms of the ways that they connect, without knowing what the objects connected by the arrows actually do.
In practice, the objects that we connect by arrows are usually some kind of aggregate: sets, types, spaces, topologies; and the arrows represent some kind of mapping – a function, or a transformation of some kind. We’re reasoning about these aggregates by reasoning about how mappings between the aggregates behave.
But if the objects represent some abstract concept of collections or aggregates, and we’re trying to reason about them, sometimes we need to be able to reason about what’s inside of them. Thinking in arrows, the only way to really be able to reason about a concept like membership, the only way we can look inside the structure of an object, is by finding special arrows.
The point of the definitions we just looked at is to give us an arrow-based way of peering inside of the objects in a category. These tools give us the ability to create constructions that let us take the concept of something like membership in a set, and abstract it into an arrow-based structure.
Reasoning in arrows, a terminal object is an object in a category that captures a concept of a single object. It’s easiest to see this by thinking about sets as an example. What does it mean if an object, T, is terminal in the category of sets?
It means that for every set , there’s exactly one function from to . How can that be? If is a set containing exactly one value , then from any other set , the only function from is the constant function . If had more than one value in it, then it would be possible to have more than one arrow from to – because it would be possible to define different functions from to .
By showing that there’s only one arrow from any object in the category of sets to T, we’re showing that can’t possibly have more than one object inside of it.
Knowing that, we can use the concept of a terminal object to create a category-theoretic generalization of the concept of set membership. If is an element of a set , then that set membership can be represented by the fact that there is an arrow from the terminal object to . In general, for any object in a category, if there is an arrow from a terminal object to , then in some sense, .
Constructions
We’re finally getting close to the real point of category theory. Category theory is built on a highly abstracted notion of functions – arrows – and then using those arrows for reasoning. But reasoning about individual arrows only gets you so far: things start becoming interesting when you start constructing things using arrows. In lesson one, we saw a glimpse of how you could construct a very generalized notion of monoid in categories – this is the first big step towards understanding that.
Products
Constructions are ways of building things in categories. In general, the way that we work with constructions is by defining some idea using a categorical structure – and then abstracting that into something called a universal construction. A universal construction defines a new category whose objects are instances of the categorical structure; and we can understand the universal construction best by looking at the terminal objects in the universal construction – which we can understand as being the atomic objects in its category.
When we’re working with sets, we know that there’s a set-product called the cartesian product. Given two sets, A and B, the product
The basic concept of a product is really useful. We’ll eventually build up to something called a closed cartesian category that uses the categorical product, and which allows us to define the basis of lambda calculus in category theory.
As usual, we want to take the basic concept of a cartesian product, and capture it in terms of arrows. So let’s look back at what a cartesian product is, and see how we can turn that into arrow-based thinking.
The simple version is what we wrote above: given two sets A and B, the cartesian product maps them into a new set which consists of pairs of values in the old set. What does that mean in terms of arrows? We can start by just slightly restating the definition we gave above: For each unique value , and each unique value , there’s a unique value .
But what do we actually mean by ? Mathematicians have come up with a lot of different ways of constructing ordered pairs. But we want to create a general model of an ordered pair, so we don’t want to limit ourselves to any specific construction: we want to capture the key property of what the ordered pair means.
It doesn’t matter which one we use: what matters is that there’s a key underlying property of the product: there are two functions and, called projection functions, which map elements of the product back to the elements of A and B. If , then (where is the name of the left projection), and (where is the name of the right projection).
That’s going to be the key to the categorical product: it’s going to be defined primarily by the projection functions. We know that the only way we can talk about things in category theory is to use arrows. The thing that matters about a product is that it’s an object with projections to its two parts. We can describe that, in category theory, as something that we’ll call a wedge:
A wedge is basically an object, like the one in the diagram to the right, which we’ll call . This object has two special arrows, and , that represent projections from to its components in and .
Now we get to the tricky part. The concept of a wedge captures the structure of what we mean by a product. But given two objects A and B, there isn’t just one wedge! In a category like Set, there are many different ways of creating objects with projections. Which object is the correct one to use for the product?
For example, I can have the set of triples . I can easily define a left project from to , and a right projection from to . But clearly is not what we mean by the product of . It’s close, but it’s got extra noise attached, in the form of that third element .
If, for two objects and , there are many wedges with left and right projections, which one is the real product?
Just a little while ago, we talked about initial and terminal objects. A terminal object can be understood as being a rough analog to a membership relation. We’re going to use that.
We can create a category of wedges , where there is an arrow from to when the diagram below commutes in our original category:
In the category of wedges, what that means is that Y is at least as strict of a wedge than X; X has some amount of noise in it (noise in the sense of the C element of the triple from the example above), and Y cannot have any more noise than that. The true categorical product will be the wedge with no excess noise: an wedge which has an arrow from every other wedge in the category of wedges.
What’s an object with an edge from every other object? It’s the terminal object. The categorical product is the terminal wedge: the unique (up to isomorphism) object which is stricter than any other wedge.
Another way of saying that, using categorical terminology, is that there is a universal property of products: products have left and right projections. The categorical product is the exemplar of that property: it is the unique object which has exactly the property that we’re looking at, without any extraneous noise. Any property that this universal object has will be shared by every other product-like object.
This diagram should look familiar: it’s the same thing as the diagram for defining arrows in the category of wedges. It’s the universal diagram: you can substitute any wedge in for C, along with its project arrows (f, g).
We can pull that definition back to our original category, and define the product without the category of wedges. So given two objects, A and B, in a category, the categorical product is defined as an object which we’ll call along with two arrows and , which have the property that for any object which has arrows and , there is a unique arrow for which the diagram to the right commutes.
On its own, if we’re looking specifically at sets, this is just a complicated way of defining the cartesian product of two values. It doesn’t really tell us much of anything new. What makes this interesting is that it isn’t limited to the cartesian product of two sets: it’s a general concept that takes what we understand about simple sets, and expands it to define a product of any two things in categories. The set-theoretic version only works for sets: this one works for numbers, posets, topologies, or anything else.
In terms of programming, products are pretty familiar to people who write functional programs: a product is a tuple. And the definition of a tuple in a functional language is pretty much exactly what we just described as the categorical product, tweaked to make it slightly easier to use.
For example, let’s look at the product type in Scala.
trait Product extends Any with Equals {
def productElement(n: Int): Any
def productArity: Int
...
}
The product object intrinsically wraps projections into a single function which takes a parameter and returns the result of applying the projection. It could have been implemented more categorically as:
trait CatProduct extends Any with Equals {
def projection(n: Int): () => Any
...
}
Implemented the latter way, to extract an element from a product, you’d have to write prod.projection(i)()
which is more cumbersome, but does the same thing.
More, if you look at this, and think of how you’d use the product trait, you can see how it relates to the idea of terminal objects. There are many different concrete types that you could use to implement this trait. All of them define more information about the type. But every implementation that includes the concept of product can implement the Product trait. This is exactly the relationship we discussed when we used terminal objects to derive the ideal product: there are many abstractions that include the concept of the product; the categorical product is the one that abstracts over all of them.
The categorical product, as an abstraction, may not seem terribly profound. But as we’ll see in a the next post, in category theory, we can compose abstractions – and by using composition to in a compositional way, we’ll be able to define an abstraction of exponentiation, which generalizes the programming language concept of currying.