Functors

Posted on Mar 20, 2024

Having two categories $C$ and $D$, can we take objects and morphisms from $C$ and map them to objects and morphisms in $D$? A functor is just that, a mapping between categories.

Functor laws

There are laws though, we can’t just map things at random.

Functors preserve composition

Let $f : a \to b$ and $g : b \to c$ two arrows in $C$.

$$F(g \circ f) = F(g) \circ F(f)$$

Functors preserve identity

$$F(id_a) = id_F$$

Functors in Haskell

When programming with Haskell, we’re working with only the category of all Haskell types, called Hask, so our functors look more like $F : Hask \to Hask$ – notice that the source and target categories are the same.

An object in Hask is just a type (e.g. Int, Bool, Char, and so on). Our functor $F$, when mapping Hask objects, will be a mapping between two types – $Type \to Type$. These mappings are just type constructors with arguments.

e.g. Maybe a : Type -> Type takes a type as an argument maps it to another type. Taking a concrete example, Maybe a maps the type Int : Type to Maybe Int : Type.

So we can see that mapping of objects is handled by type constructors with parameters. Our next stop is mapping morphisms, which in Hask are just functions.

What does it mean to map a function? Having a -> b, we want to map it to a function F a -> F b. This is encoded by the Functor type class:

class Functor f where
	fmap :: (a -> b) -> (f a -> f b)

Example of functors in Haskell

Let’s consider the object Int, and the arrow Int -> Char, both from Hask.

Maybe

data Maybe a = Nothing | Just a

Object Int maps to Maybe Int by the type constructor.

Arrow Int -> Char maps to Maybe Int -> Maybe Char by fmap

Reader

type Reader r a = r -> a

Notice how reader has two arguments. A functor always takes a single argument, so Reader is a functor only in its last argument.

Also note that Reader r a is the “same” as a function r -> a.

Object Int maps to Reader r Int, a function r -> Int.

Arrow Int -> Char maps to Reader r Int -> Reader r Char, a function (r -> Int) -> (r -> Char)

Const

data Const c a = Const c

This type is the same as just having a value c. Notice that a appears only at the type level.

Object Int maps to Const c Int, always the value c.

Arrow Int -> Char maps to Const c Int -> Const c Char, always a function c -> c.

So Const completely ignores a at the value level and maps everything to c.

Functor composition

Having two functors $F : B \to C$ and $G : A \to B$, we can compose them, gaining another functor $F \circ G : A \to C$.

In Haskell, this is defined by the Compose data type and its Functor instance.

Compose (f g : Type -> Type) (a : Type) = Compose (f (g a))

A function a -> b maps to Compose f g a -> Compose f g b, which is the same thing as f (g a) -> f (g b).

Functor composition is associative

Consider three functors $F : C \to D$, $G : B \to C$ and $H : A \to B$.

$$F \circ (G \circ H) = (F \circ G) \circ H$$

Functor composition preserves identity

$$F \circ Id = F$$