Scala Cats Contravariant Functors
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.