Scala Cats Invariant Functor
Today, I have another kind of Functor for you — the Invariant Functor! There were some posts already touching on Covariant Functors (simply called “Functor”) or Contravariant Functors like the one here. If you understand the concept of Covariant and Contravariant types of functors, then the Invariant will be easy to grasp as it combines functionality from both the functors mentioned above.
As you probably remember, with Functors we can map one type into another with a given function f
:
With Contravariant Functors, we do the same but with types in f
switched:
which gets useful when we want to provide new implicit implementations of some type class by reusing implementations already available.
In short, Functor’s map
is used when we append operations in a chain and Contravariant Functor’s contramap
is used when we want to prepend them. There is a third option that allows us to go in both directions with imap
and this is specific to the Invariant Functor type.
Invariant functors define a method called imap
similarly to map
and contramap
in the previously mentioned functor types. Imap
is all about generating new type classes via a pair of bidirectional transformations.
The most common and easiest example of Invariant Functors include codecs and parsers, where we need to have the ability to transform something in both directions.
In Cats library, the definition for imap
in Invariant
functor looks similar:
we can implement our imap
in terms of available encode
and decode
methods:
This way, having the encode/decode
method implementations, we can provide imap
which will allow us to create new CustomParser
instances for other types easily, for example:
Let’s say we have an implementation of our CustomParser
for Long
values in our implicit scope and we want to get another parser but for java.util.Date
type. We know how to convert Date
to and from a Long
value, therefore using the Invariant Functor idea, we can provide a new parser very easily:
This is pretty similar to the already discussed Covariant and Contravariant Functors. One thing to note here is that Covariant and Contravariant Functors are actually descendants of the Invariant Functor, in other words the imap
function can be implemented for them using their map
or contramap
respectively.
From Cats Invariant documentation:
Every covariant (as well as contravariant) functor gives rise to an invariant functor, by ignoring the
g
(or in case of contravariance,f
) function.
where f
and g
correspond to our dec
and enc
functions.
Cats documentation mentions another good example of creating new instances of Invariant using Semigroup
type class.
Having a Semigroup[Long]
already provided by Cats, we can easily add new Semigroup instances for new types when we know how to convert from one type to another and back. The example is using Long -> Date
and Date -> Long
transformations we have used earlier:
Hope that makes things even clearer. Watch the SoftwareMill blog space for more entries on Scala, Cats, and Functional Programming in general.