Scala Cats Contravariant Functors

Krzysztof Grajek
SoftwareMill Tech Blog
3 min readFeb 8, 2021

--

TH @ flickr.com

In this post, we’ll talk (again) about Functors — the ones from Scala Cats library, but not the classic Functors like we all know and love, we’ll cover a bit different kind of Functors called Contravariant Functors — depicted in Cats itself as a type class named just that: Contravariant.

Some of you might not know this, but the classic Functor with map operation we use every day in our Scala projects seasoned with Cats is in fact a Covariant Functor. Bear in mind that the term ‘Variance’ as applied to Functor types has nothing to do with different kinds of variance we know when talking about types and generic polymorphism.

A Typical Functor, in Scala FP terms, represents a type class operating on higher-kinded types, which proves pretty useful when we want to abstract and generalize our APIs.

Just for the sake of completeness, as we won’t talk about classic Functors here, I will provide a simple example below:

The generalized version of reverse is not concerned with wrapper type we want to use as long as it can find a Functor instance for it. With Cats, this usually happens with an import, eg. for Option:

Ok, so I’m pretty sure you all knew that already. Functors are everywhere and we use them a lot, Applicatives are Functors, Monads are Functors, even a simple one param function (Function1) is a Functor too. Functor is all about a map method transforming a wrapped value of type A into type B and preserving the wrapper itself.

But…, believe it or not, there are times where we want to reverse types in the Functor function f so it will take type B and return type A but the function itself will still return wrapper of type B — confused? If so, read on as this post is for you :).

Imagine a simple type class responsible for the transformation of some type T into a Boolean — popular filtering. Let’s define it as a simple Filter trait for now:

To work with our type class, we will create a very simple instance and an interface so we can use it to prove the point:

The implementation is not important here, so excuse me for not inventing anything more advanced :). Of course, the first println outputs false and the second one outputs the value true.

Now imagine that you need a Functor functionality for our Filter type class to convert it from Filter[String] to Filter[Int] using a map method like we usually do:

Do you see the problem we have here? We cannot pass value to our f mapping function as we cannot use type B in this case as an input to the function f. What we need here is an A type.

In other words, we want to compose A => Boolean with B => Boolean having a function A => B.

So, how do we do that? We need to use a Contravariant Functor instead of a Covariant one. The type class in Cats for doing just that is named simply Contravariant.

As you have probably noticed, the input type parameter and the output types are switched compared to our old and well known map operation.

Composing with Contravariant is as easy as with typical Covariant Functor:

Similarly, we can use Contravariant to work with wrapped values in a context like Option, typical example with Cats Show[_]:

or even a shorter version:

In the same manner, we can use any types wrapped in Option for our filters:

Or even better, getting rid of _.get :

Ok, so that’s all when it comes to Contravariant Functors. I hope that I have made it simpler and after reading this, you can add one more type class to your programming toolbox. There are, of course, a number of other interesting type classes in our beloved Cats library, even one more interesting Functor type :) but I will leave that for later.

--

--