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 = idfmap (g . f) = fmap g . fmap fBifunctor 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 = idbimap (f . g) (h . i) = bimap f h . bimap g iWhen 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 = iddimap (h . f) (g . i) = dimap f g . dimap h iProfunctors 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