Skip to content

Commit

Permalink
Documentation for the Invariant typeclass
Browse files Browse the repository at this point in the history
  • Loading branch information
markus1189 committed Dec 2, 2015
1 parent 13960c1 commit 620b971
Showing 1 changed file with 80 additions and 0 deletions.
80 changes: 80 additions & 0 deletions docs/src/main/tut/invariant.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
---
layout: default
title: "Invariant"
section: "typeclasses"
source: "https://github.com/non/cats/blob/master/core/src/main/scala/cats/functor/Invariant.scala"
scaladoc: "#cats.functor.Invariant"
---
# Invariant

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

```scala
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 ignorig the `f` (or in case of contravariance, `g`) 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`
typeclass, we could use `map` to apply a function `longToDate`:

```tut:silent
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`:

```tut:silent
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 typeclass for this and it is called `Invariant`.
Instances of the `Invariant` typeclass provide the `imap` function:

```scala
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`.

0 comments on commit 620b971

Please sign in to comment.