Invariant

API Documentation: Invariant

The Invariant type class is for functors that define an imap function with the following type:

def imap[A, B](fa: F[A])(f: A => B)(g: B => A): F[B]

Every covariant (as well as contravariant) functor gives rise to an invariant functor, by ignoring the g (or in case of contravariance, f) function.

Examples for instances of Invariant are Semigroup and Monoid, in the following we will explain why this is the case using Semigroup, the reasoning for Monoid is analogous.

Invariant instance for Semigroup

Pretend that we have a Semigroup[Long] representing a standard UNIX timestamp. Let's say that we want to create a Semigroup[Date], by reusing Semigroup[Long].

Semigroup does not form a covariant functor

If Semigroup had an instance for the standard covariant Functor type class, we could use map to apply a function longToDate:

import java.util.Date
def longToDate: Long => Date = new Date(_)

But is this enough to give us a Semigroup[Date]? The answer is no, unfortunately. A Semigroup[Date] should be able to combine two values of type Date, given a Semigroup that only knows how to combine Longs! The longToDate function does not help at all, because it only allows us to convert a Long into a Date. Seems like we can't have an Functor instance for Semigroup.

Semigroup does not form a contravariant functor

On the other side, if Semigroup would form a contravariant functor by having an instance for Contravariant, we could make use of contramap to apply a function dateToLong:

import java.util.Date
def dateToLong: Date => Long = _.getTime

Again we are faced with a problem when trying to get a Semigroup[Date] based on a Semigroup[Long]. As before consider the case where we have two values of Date at hand. Using dateToLong we can turn them into Longs and use Semigroup[Long] to combine the two values. We are left with a value of type Long, but we can't turn it back into a Date using only contramap!

Semigroup does form an invariant functor

From the previous discussion we conclude that we need both the map from (covariant) Functor and contramap from Contravariant. There already is a type class for this and it is called Invariant. Instances of the Invariant type class provide the imap function:

def imap[A, B](fa: F[A])(f: A => B)(g: B => A): F[B]

Reusing the example of turning Semigroup[Long] into Semigroup[Date], we can use the g parameter to turn Date into a Long, combine our two values using Semigroup[Long] and then convert the result back into a Date using the f parameter of imap:

import java.util.Date

// import everything for simplicity:
import cats._
import cats.syntax.all._

def longToDate: Long => Date = new Date(_)
def dateToLong: Date => Long = _.getTime

implicit val semigroupDate: Semigroup[Date] =
  Semigroup[Long].imap(longToDate)(dateToLong)

val today: Date = longToDate(1449088684104l)
val timeLeft: Date = longToDate(1900918893l)
today |+| timeLeft
// res1: Date = Thu Dec 24 20:40:02 UTC 2015