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