- A+

This answer from a Category Theory perspective includes the following statement:

...the truth is that there's no real distinction between co and contravariant functor, because every functor is just a covariant functor.

...

More in details a contravariant functor F from a category C to a category D is nothing more than a (covariant) functor of type F : C

^{op}→D, from the opposite category of C to the category D.

On the other hand, Haskell's `Functor`

and `Contravariant`

merely require `fmap`

and `contramap`

, respectively, to be defined for an instance. This suggests that, from the perspective of Haskell, there exists objects that are `Contravariant`

but are not `Functor`

s (and vice versa).

So it seems that in Category Theory "there's no real distinction between co and contravariant functors" while in Haskell there is a distinction between `Contravariant`

and `Functor`

.

I suspect that this difference has something to with all implementation in Haskell happening in Hask, but I'm not sure.

I think I understand each of the Category Theory and Haskell perspectives on their own, but I'm struggling to find an intuition that connects the two.

It's for convenience.

One could get by with a more general `Functor`

class, and define instances for endofunctors on Hask (corresponding to our existing `Functor`

) and functors from Hask^op to Hask (corresponding to our existing `Contravariant`

). But this comes at a figurative cognitive cost and a quite literal syntactical cost: one must then rely on type inference or type annotations to select an instance, and there are explicit conversions (named `Op`

and `getOp`

in the standard library) into and out of Hask^op.

Using the names `fmap`

and `contramap`

relaxes both costs: readers do not need to run Hindley-Milner in their head to decide which instance is being selected when it is unambiguous, and writers do not need to give explicit conversions or type annotations to select an instance in cases where it is ambiguous.

(I am actually rewriting history a little bit here. The *real* reason is because the language designers thought the specialized `Functor`

would be useful and hadn't imagined or didn't see a need for a more general `Functor`

. People came along later and noticed it would be useful, sometimes. But experience with the generalized `Functor`

class shows that can be tedious, and that specialized classes for the most common cases turns out to be a surprisingly good fit after all, for the reasons described above.)