Skip to content

Functors and Monads with Category Theory

Clarence Chew edited this page Nov 8, 2021 · 3 revisions

Functors and Monads with Category Theory

Category

A category consists of objects and arrows/morphisms between objects. There may be more than one arrow between objects, and arrows may point from an object to itself.

  1. You can compose arrows a:X->Y and b:Y->Z to make an arrow (b after a):X->Z, and arrow composition is associative.
  2. There is an identity arrow e:X->X that when composed with an arrow a:X->Y, (a after e) = a and when composed with an arrow b:Y->X, (e after b) = b.

Why is category theory useful?

Let's view all the object types in Java as the objects of a category. The pure functions can form arrows, based off the input type and output type of the functions. Functions can be composed, and the identity function can be defined for every type.

Functor

A functor is a mapping from the objects and arrows of a category to the objects and arrows of another category that preserves structure. That is, if arrow a:X->Y and b:Y->Z are arrows in category C, then F(a):F(X)->F(Y) and F(b):F(Y)->F(Z) are arrows in the category F(C). Since structure is preserved, F(b after a)=F(b) after F(a). Functors are also known as covariant functors. However, if structure is preserved in the reverse direction, such that F(b after a)=F(a) after F(b), this is a contravariant functor. These two terms also get used in generics, using a slightly different category.

Functor (as a programming concept)

We consider the Optional functor mapping the object type T to the object type Optional<T>. Every object type can be mapped, using Optional.of (or Optional.ofNullable). A function f can be changed into x->x.map(f) so that it can work with an Optional<> input.

In general, generics that support mapping are essentially functors. Consider mapping T to Stream<T>, and every element in T can be mapped using a function.

Covariant and Contravariant Functors

Consider the category of all Java types, where arrows go from superclasses to subclasses. We take a class to be super or extends itself. Essentially, this category demonstrates the idea of substitution, where superclass compile time types can store subclass runtime types.

However, if we consider the type of a Consumer, then subclass types can be replaced with superclass types, drawing the arrows in the opposite direction on. The idea of reversing the direction of the arrows is related to the notion of a contravariant functor. If the arrows remain the same direction, that makes it a covariant functor.

We observe how generic types are defined.

<generic-types> return-type function-name(generic-type identifier, ...) {
    // function body
}

Some of the types may need to be related to each other, for example, I might need T extends Number. We can view this as a simple category of two objects, T and Number, and a nontrivial arrow from Number to T. We need to find a correspondence where this category matches up with the category of all Java types, that is a functor.

Consider making an array T[]. It can store any object which is T or of a subclass of T.

Object[] objArr = new Integer[30]; // okay 
objArr[0] = ""; // oh no
Clone this wiki locally