Source: bartoszmilewski.com

In the past years there’s been a lot of talk about category theory in the functional programming community. This is because category theory provides a general setting to speak about computation and it gives us concepts such as Functor and Monad which allow us to abstract of lot of interesting patterns (error handling, non-determinism, state management, etc.).

Still, programming is just one possible application for category theory, which could be in turn used to talk about a great variety of subjects. For example, you could take a look at the work of Baez and Spivak on Applied Category Theory to find out about other fields which could benefit from a categorical point of view.

As we said, category theory concepts are quite widespread in functional programming, but what if we were to make category theory the object of a software library? If we do that, then we are able to apply its ideas and its tools to a variety of settings, and not only use it to structure our software. That is quite a change of perspective, isn’t it? For example we could start applying our category theory library to talk about electric circuits, chemistry, biology, or, as we are doing here at Statebox, to use Petri nets to model a visual programming language.

If we want to do that, we need a programming language which allows us to encode all the laws and properties of the mathematical definition in the language itself, so that we can actually prove things, not just have the illusion that they hold. To do this, we need a language with a powerful type system. In particular, we need the full power of dependent types. Since we are really interested in using our category theory implementation for practical purposes, we chose Idris, which provides a nice trade-off between proving facilities and the ability to interact with the real world.

So, What’s a Category?

If you look it up on Wikipedia, a category is a collection of “objects” that are linked by “arrows”. A category has two basic properties: the ability to compose the arrows associatively and the existence of an identity arrow for each object. Let’s try to break down this definition to understand what it actually means and to make everything more precise.

To start with, we’re just declaring `Category` as a new type in our program, so that we could be able to define new values for that type

record Category where

A record in Idris is just the product type of several values, which are called the fields of the record. It’s a convenient syntax because Idris provides field access and update functions automatically for us.

The Elements

At its most basic level, a category is a collection of things. To specify what these things are, we add to our definition a field to keep track of them

object : Type

You can think about object as a collection of dots, which will be items we want to talk about in our category.

Next thing up, we need ways to go from one dot to the other, so that we can wander around our objects. This introduces some dynamics inside our category, which allows us to talk about movement and evolution of systems.

In practice, we need to describe, for any pair of objects a and b , the collection of arrows going from a to b . To do this, let’s add another field to our Category

morphism : object -> object -> Type

Now, for any pair of objects a, b : object , we can talk about the collection morphism a b of arrows going from a to b .

The Operations

Now that we have arrows in our category, allowing us to go from one dot to the other, we would like to start following consecutive arrows; I mean, if an arrow leads us to b , we would like to continue our journey by taking any other arrow starting at b . Nobody stops us from doing that, but it would be really cumbersome if we must keep track of every single arrow whenever we want to describe a path from one dot to another. The definition of category comes in our help here, providing us with an operation to obtain arrows from paths.

In practice, whenever we have two consecutive arrows f : morphism a b and g : morphism b c (notice how b is the target of the first arrow and the origin of the second, they must necessarily coincide!) we are able to obtain an arrow f ; g : morphism a c which is the same thing as following first f and then g .

compose : (a, b, c : object)

-> (f : morphism a b)

-> (g : morphism b c)

-> morphism a c

Translating it into human language, this says that whenever we have three objects a , b and c and morphisms f , from a to b , and g , from b to c , we can construct a morphism f ; g which goes directly from a to c .

The other basic ingredient we have in a category are identity arrows. This amounts to say that for any dot a we have a specific arrow going from a to a . The identity arrow is a way to ensure that we can always go from one dot to itself, doing a trivial move. This could seem a bit pointless in itself, but it’s actually not if you think what it means for the composition of two arrows to be an identity arrow!

We can define this in Idris as follows:

identity : (a : object) -> morphism a a

simply stating that for any object a we have a morphism identity a going from a to a .

If we stopped here with our definition, we would have all the basic components, but we would not have any assurance that they behave in any sensible manner. This means that we would have problems attaching an easy and meaningful semantics to our basic elements.

To overcome this we need to impose some laws on our elements, so that we have an assurance that they behave as we expect. Notice that this is one of the points where having a dependently typed language really helps!

The Laws

We would like to interpret the identity morphism of any object as an arrow which doesn’t do anything, which keeps everything as is. We can formalise this by saying that whenever we compose an identity morphism with any other morphism f , what we obtain is exactly f . In other terms, whenever we have an identity morphism, the result will be the same if we remove it. Or, from the other perspective, wherever we have a morphism, we can compose it with an identity without modifying the global behaviour. Formally we can state this as follows:

leftIdentity : (a, b : object)

-> (f : morphism a b)

-> compose a a b (identity a) f = f

and

rightIdentity : (a, b : object)

-> (f : morphism a b)

-> compose a b b f (identity b) = f

In short, this amounts to say that f ; (identity b) = f = (identity a) ; f for any morphism f : a -> b . As a technical side note, I’d like to emphasise here how Idris allows us to encode equality in the type system; from a practical point of view, equality in Idris is a type which has only one inhabitant, called Refl , which corresponds to reflexivity, which states that x = x for any possible x .

There is another law which we need to add, which concerns the composition of multiple arrows. In fact, considering what we have up to now, we know that we can compose two consecutive arrows. What about if we have three? Suppose we had f : a -> b , g : b -> c and h : c -> d , we could compose the first two, f and g , and then compose the result f ; g with h . Or we could compose first g and h and then compose f with the result g ; h . In this way we would obtain two morphisms, which are a priori different. What we want is actually to impose that they must be equal, so that we should not keep track of the order with which we perform the composition, but consider just the resulting path. This axiom is called associativity:

associativity : (a, b, c, d : object)

-> (f : morphism a b)

-> (g : morphism b c)

-> (h : morphism c d)

-> compose a b d f (compose b c d g h)

= compose a c d (compose a b c f g) h

Conclusion

Summing up and putting it all together, our definition of Category now looks like

record Category where

constructor MkCategory

object : Type

morphism : object -> object -> Type

identity : (a : object) -> morphism a a

compose : (a, b, c : object)

-> (f : morphism a b)

-> (g : morphism b c)

-> morphism a c

leftIdentity : (a, b : object)

-> (f : morphism a b)

-> compose a a b (identity a) f = f

rightIdentity : (a, b : object)

-> (f : morphism a b)

-> compose a b b f (identity b) = f

associativity : (a, b, c, d : object)

-> (f : morphism a b)

-> (g : morphism b c)

-> (h : morphism c d)

-> compose a b d f (compose b c d g h)

= compose a c d (compose a b c f g) h