Introduction
How do you approach learning a new foreign language? Starting with an alphabet, looking for grammar books at local shops or insulting native speakers with your distorted pronunciation? I bet there would a plenty of opinions. My approach is to put your efforts into a limited set of words and memorize the way they form valid combinations (however I’m not sure it’s going to work for Russian).
We are going to grasp some definitions of category theory in a very limited form - they are going to be defined through functors and natural transformations. There would be no full definitions of it - I’m not a mathematician and I definetely don’t know it on a decent level. Nevertheless it doesn’t really stop me using these great universal constructions in order to forge extremely composable programming language called Я.
Naturality
Did you know that mathematicians used the term natural transformation before it was ever defined? Even before categories and functors - things that are suppose to show up earlier in chronological order. For people involved in abstract algebra those days things seemed “natural” without giving any definition of “naturality” - especially when they observe commutative diagrams. Actually you can perceive “naturality” as a synonymous of “the obvious choice” or “the only one”.
The history is going to repeat itself - we will start with natural transformations, then we quickly explore functor varieties and try to express other things without even touching the foundations - categories, sets, etc. Probably true math bros are going to be outraged by these crumpled musings but this is the way (I’m not gonna lie) I perceive these definitions avoiding stepping into academia territory.
Real world first
Let’s say we have a list of users. We want to get a top user’s age.
As you can see, it doesn’t really matter if we:
- First map a List in order to transform
User
elements toInteger
and then try totop
it - First get a
top
mostUser
and then map anage
function in Maybe
users `yo` age `yi` top `q__` users `yi` top `yo` age
Or in case if you want to write it in point-free style, it’s a perfect illustration of the difference between the regular composition operator and the opposite one (ha/ho):
top `ha'yo` age `q__` top `ho'yo` age
Generalizing
Let’s take a look at this commutative diagram that generalizes the latter above:
- Instead of top morphism we have
η
(which is called a component of a natural transformation) - List and Maybe are substituted with
T
andTT
functor placeholders respectively User
andInteger
are replaces with contravarianta
and covarianto
type variables
Natural Transformation is a mapping between functors such that:
TT[f] ∘ η[a] ≡ η[o] ∘ T[f]
So as you can see on the commutative diagram above, it doesn’t really matter which way we are going to choose, they are equivalent.
Natural Transformation is a basic building block of control flow in Я. Actually everything is deriving from this term, even Category and Functor (which is definetely a noncense from theoretical point of view, but this design choice let us to use these definitions interchangeably in Haskell type system).
Functors
And as we already started to talk about functors… they are just mappings between categories:
You can see that this morphism from a
to o
is moved pretty accurately to another side of this double arrow called map
. This is how Functor looks like schematically and this is how I imagine it in my head when I do code.
But what does +
symbol is doing there? Oh, I forgot to notice - when people talk about functors they usually mean covariant ones. These “usual” functors map morphisms as it is.
List, Maybe, State - most of the functors in Я are covariant ones.
Contravariant functors are weird - they flip morphisms. Compare this diagram to the one above:
What if I tell you that there is a contravariant functor that you work on a day-to-day basis? Functions!
Actually, functions are bifunctors (bi means “two” here) - they are contravariant by a first argument and covariant by a second one!
So… how all this theoretical stuff is related to a language itself? Operators!
Operators
In Я all operarator names on bifunctors with a function-like parameter arrangement (T[-,+]
) start with h
token - which means Hom Functor: ho, ha, hu, hv.
We can define map
expression like this, so you can spot the difference between a covariant map (+
) and a contravariant one (-
):
map[+]: from a o `AR_` into (t a) (t o)
map[-]: from o a `AR_` into (t a) (t o)
In the beginning of this article you should stumble upon this piece of code:
users `yo` age `yi` top `q__` users `yi` top `yo` age
Since both List and Maybe are covariant functors we use a circle with a bullet point inside (yo).
`yo` (yo): t a `AR_` into (from a o) (t o)
Yoneda lemma
But wait… aren’t these definitions are different?
map[+]: from a o `AR_` into (t a) (t o)
`yo` : t a `AR_` into (from a o) (t o)
What if I tell you that these definitions are kinda… the same? “Kinda”, because they are naturally isomorphic. This is one of most significant theoretical result of reasoning on functors and natural transformations - Yoneda lemma.
We will talk about it next time.
Continue: Using Yoneda lemma for flipping notation
In case if you want to go deeper into this topic: