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:
fmap id = id
fmap (g . f) = fmap g . fmap f
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:
bimap id id = id
bimap (f . g) (h . i) = bimap f h . bimap g i
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)
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:
dimap id id = id
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