Contravariance and Profunctors

Functor

Functors are used to map categories. In haskell they are defined as:

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

And they follow the laws:

  1. fmap id = id
  2. fmap (g . f) = fmap g . fmap f

Bifunctor

Bifunctor is a functor whose domain is a product category. A functor $F: C_1 \times C_2 \rightarrow D$ is a Bifunctor.

class Bifunctor f where
    bimap :: (a -> c) -> (b -> d) -> f a b -> f c d

they also follow similar laws:

  1. bimap id id = id
  2. bimap (f . g) (h . i) = bimap f h . bimap g i

Contravariance

When we have some functor $F: C \rightarrow D$, $F$ is covariant to objects in $C$. Covariance and Contravariance here can be differentiated through type polarity, which essentially states whether the type is the output or the input to a function.

Example of contravariant object:

newtype Predicate a = Predicate { runPredicate :: a -> Bool }

We cannot use a functor to map Predicate, since it is Contravariant in type a. Instead, we can introduce "Contravariant functor" to solve this.

class Contravariant f where
    contramap :: (b -> a) -> f a -> f b

instance Contravariant Predicate where
    contramap f (Predicate p) = Predicate (p . f)

Profunctor

While a Bifunctor is covariant in its type parameters, a Profunctor is covariant in its right type parameter and contravariant in its left type parameter.

class Profunctor p where
    dimap :: (a -> b) -> (c -> d) -> p b c -> p a d

Laws are similar to bifunctor:

  1. dimap id id = id
  2. dimap (h . f) (g . i) = dimap f g . dimap h i

Profunctors can model the function arrow:

instance Profunctor (->) where
    dimap ab cd bc = cd . bc . ab


(Where ab is of type (a -> b), and similarly for cd and bc)

Lens also composes through profunctors, and I'll probably write about it when I understand it better.

[1] The extended functor family
[2] Category Theory for Programmers - Bartosz Milewski