## Covariance and Contravariance, functionally

From Covariance and Contravariance we know that generic classes can be made covariant or contravariant. In fact, the same goes for functors. Consider the following type constructor List in Haskell.

data List a = Nil | Cons a (List a)


It’s a covariant functor because we could define a function fmap which takes a function from a to b and returns another function from List a to List b.

fmap :: (a -> b) -> (List a -> List b)


This is the definition of fmap

instance Functor List where
fmap _ Nil = Nil
fmap f (Cons x t) = Cons (f x) (fmap f t)


A contravariant functor is, by contrast, a type constructor f which we could define a contramap taking a function b -> a and returning another one f a -> f b.

contramap :: (b -> a) -> (f a -> f b)


An example of contravariant functors is the so called Writer type, which is essentially a function when the return type is fixed, or, Op r as defined below.

type Op r a = a -> r


The contramap is defined as

instance Contravariant (Op r) where
-- (b -> a) -> Op r a -> Op r b
-- here f is the b -> a and g is the Op r a (or a -> r)
contramap f g = g . f


Notice that f applies before g. Since f is b -> a and g is Op r a (or a -> r) and the result type is Op r b (or b -> r), we should apply f first to convert b to a and then g which converts a to r. In other words, the contramap function of the writer type “inserts” the function b -> a before the function a -> r.

## Covariance and Contravariance, categorically

We know that in category theory, a functor is a mapping between categories that preserves their structures. A functor $$F$$ between two categories is shown below. However, in programming we generally have only one category. Below is how covariant and contravariant functors work in only one category. A covariant functor maps a -> b to F a -> F b, and a contravariant functor maps a -> b to F b -> F a. Note that the word functor is used in both category theory and functional programming. To avoid confusion I refer to functors in functional programming as functional functors. When a single word functor is used, it means functors in category theory.

Below is how covariant and contravariant functors work from the categories perspective. Here the objects are types (like Int and String) and the morphisms are functions between types (there are a lot of morphisms between Int and String, but only parseInt is shown as an example). In this example, both List and Op Bool (a -> Bool) are functors. The List functor maps any type a to type List a and any function f :: a -> b to fmap f :: List a -> List b by applying function f to every element of the list. It preserves the direction of morphisms so it’s covariant. In contrast, the Op Bool functor maps any type a to function type a -> Bool and any function f :: a -> b to contramap f :: (b -> Bool) -> (a -> Bool) by “inserting” itself before the b -> Bool function. It reverses the direction of morphisms so it’s called contravariant.

On the other hand, below is how covariant and contravariant generics work. Here the objects are still types, but the morphisms are subtype relations. In contrast to functional functors, there are only at most one morphism between objects in this category. In this example, both List and Printer are functors, but works on a different category than the example above. The List functor maps any type a to type List[a] and any subtype relation a < b to List[a] < List[b] (here < means “subtype of”). It preserves the direction of morphisms so it’s covariant. In contrast, the Printer functor maps any type a to Printer[a] and any relation a < b to Printer[b] < Printer[a]. It reverses the direction of morphisms so it’s called contravariant.

In conclusion, Both functional functors and generic classes are actually functors, and the words covariant and contravariant in both functors and generics essentially describe the covariance/contravariance of the underlying categorical functors. However, they work on different categories. For functors the morphisms are functions but for generics the morphisms are subtype relations.