Scala Cats Invariant Functor

Krzysztof Grajek
SoftwareMill Tech Blog
3 min readMar 1, 2021

--

Dragan @ Flickr.com

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.

--

--