The type blows my mind:
class Contravariant (f :: * -> *) where
contramap :: (a -> b) -> f b -> f a
Then I read this, but con
I know this answer won't be as deeply academic as the other ones, but it's simply based on the common implementations of contravariant you'll come across.
First, a tip: Don't read the contraMap function type using the same mental metaphor for f as you do when reading the good ol' Functor's map.
You know how you think:
"a thing that contains (or produces) an
t"
...when you read a type like f t?
Well, you need to stop doing that, in this case.
The Contravariant functor is "the dual" of the classic functor so, when you see f a in contraMap, you should think the "dual" metaphor:
f tis a thing that CONSUMES at
Now contraMap's type should start to make sense:
contraMap :: (a -> b) -> f b ...
...pause right there, and the type is perfectly sensible:
b.b.First argument cooks the b. Second argument eats the b.
Makes sense, right?
Now finish writing the type:
contraMap :: (a -> b) -> f b -> f a
So in the end this thing must yield a "consumer of a".
Well, surely we can build that, given that our first argument is a function that takes an a as input.
A function (a -> b) should be a good building block for building a "consumer of a".
So contraMap basically lets you create a new "consumer", like this (warning: made up symbols incoming):
(takes a as input / produces b as output) ~~> (consumer of b)
contraMap (i.e. (a -> b)).f b).contraMap (a thing that knows how to consume an a, i.e. f a).