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 Long
s! 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 Long
s 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