The type blows my mind:
class Contravariant (f :: * -> *) where
contramap :: (a -> b) -> f b -> f a
Then I read this, but con
First of all @haoformayor's answer is excellent so consider this more an addendum than a full answer.
One way I like to think about Functor (co/contravariant) is in terms of diagrams. The definition is reflected in the following ones. (I am abbreviating contramap
with cmap
)
covariant contravariant
f a ─── fmap φ ───▶ f b g a ◀─── cmap φ ─── g b
▲ ▲ ▲ ▲
│ │ │ │
│ │ │ │
a ────── φ ───────▶ b a ─────── φ ──────▶ b
Note: that the only change in those two definition is the arrow on top, (well and the names so I can refer to them as different things).
The example I always have in head when speaking about those is functions - and then an example of f
would be type F a = forall r. r -> a
(which means the first argument is arbitrary but fixed r
), or in other words all functions with a common input.
As always the instance for (covariant) Functor
is just fmap ψ φ
= ψ . φ`.
Where the (contravariant) Functor
is all functions with a common result - type G a = forall r. a -> r
here the Contravariant
instance would be
cmap ψ φ = φ . ψ
.
But what the hell does this mean
φ :: a -> b
and ψ :: b -> c
usually therefore (ψ . φ) x = ψ (φ x)
or x ↦ y = φ x
and y ↦ ψ y
makes sense, what is ommited in the statement for cmap
is that here
φ :: a -> b
but ψ :: c -> a
so ψ
cannot take the result of φ
but it can transform its arguments to something φ
can use - therefore x ↦ y = ψ x
and y ↦ φ y
is the only correct choice.
This is reflected in the following diagrams, but here we have abstracted over the example of functions with common source/target - to something that has the property of being covariant/contravariant, which is a thing you often see in mathematics and/or haskell.
covariant
f a ─── fmap φ ───▶ f b ─── fmap ψ ───▶ f c
▲ ▲ ▲
│ │ │
│ │ │
a ─────── φ ──────▶ b ─────── ψ ──────▶ c
contravariant
g a ◀─── cmap φ ─── g b ◀─── cmap ψ ─── g c
▲ ▲ ▲
│ │ │
│ │ │
a ─────── φ ──────▶ b ─────── ψ ──────▶ c
In mathematics you usually require a law to call something functor.
covariant
a f a
│ ╲ │ ╲
φ │ ╲ ψ.φ ══▷ fmap φ │ ╲ fmap (ψ.φ)
▼ ◀ ▼ ◀
b ──▶ c f b ────▶ f c
ψ fmap ψ
contravariant
a f a
│ ╲ ▲ ▶
φ │ ╲ ψ.φ ══▷ cmap φ │ ╲ cmap (ψ.φ)
▼ ◀ │ ╲
b ──▶ c f b ◀─── f c
ψ cmap ψ
which is equivalent to saying
fmap ψ . fmap φ = fmap (ψ.φ)
whereas
cmap φ . cmap ψ = cmap (ψ.φ)