Contravariant Functors

With contravariant functors, we map over the a in (a -> b). In other words, the input, not the output. We usually need to do this when the b is a concrete type that we can’t change and the a is stuck inside a type already. Um. er. Let’s just look at some examples to get a feel.

Predicates

Let’s make ourselves a Pred type:

We call its one property run because it’s holding a function inside the type. This function must be (a -> Bool) which is parameterized on its input, but concrete on its output. This is the typical shape of a contravariant functor as we’ll be mapping over the input, not the output.

For a type to be a contravariant functor, we must provide a method called contramap :: (b -> a). Here it is for Pred:

Contrast this with the covariant function functor (say that 10 times quickly in a mirror to summon Kmett). It simply composes g beforehand, which has the effect of transforming our a before it gets to the (a -> Bool) function. We’re contravariant baby!

I find it’s much cooler to see in use if we first make it a monoid so let’s do that before playing with it.

Pred Monoid

We’ll concat two Preds by running both and keeping the conjunction. We should make the empty method always returns true so it acts as identity.

This is basically the All monoid under the hood. We could make it more generic by calling concat on the return values or parameterizing the underlying monoid, but let’s stick with this for simplicity.

Pred Examples

Now to use this crazy thing:

What’s so cool about this is that we can make a flexible rule engine. We combine multiple predicate functions with concat and map over the input before we have to return the Bool. This isn’t specific to Array — we could apply this to Stream or Tree or Option or other types that have a filter method (which can be derived from any foldable monoid).