# Profunctor

Profunctors are bifunctors that are contravariant in their first type argument

and covariant in their second one

In order to understand the meaning of the sentence, we need to know the foundations of a Profunctor

## Type Constructor

Type constructor is basically a type level function (function that acts on types rather on values), when given a type creates a new type.

`List`

is a type constructor. By itself it’s not a valid type.

It takes a type (often called type parameter) in order to create an actual type of List.

For example in order to create a `List[String]`

, we need to supply the List type constructor a type argument of `String`

.

```
List[+A] -> String -> List[String]
```

```
val list: List[String] = List[String]("A", "B", "C")
// ^ ^^
// type constructor type argument
```

## Functor

Functor (a.k.a Covariant Functor) is a type class for type constructors that define a map function

```
trait Functor[F[_]] {
def map[A, B](fa: F[A])(f: A => B): F[B]
}
```

In order to create an instance of a `Functor`

you must provide it a type constructor that takes one type parameter,
Where's the `F[_]`

is the underlying type constructor

Example implementation for an instance of a Functor for `List`

```
import cats.Functor
implicit val functorForList: Functor[List] = new Functor[List] {
def map[A, B](list: List[A])(f: A => B): List[B] = list.map(f)
}
def usageOfFunctorForList(list: List[Int])(implicit F: Functor[List]): List[Int] =
F.map(list)(_ + 1)
usageOfFunctorForList(List(1, 2, 3))
// res0: List[Int] = List(2,3,4)
```

The intuition for the map function is, if you have a context of `F[A]`

, and a function from `A -> B`

,
and you also can unwrap the value `A`

from its content/wrap a value `A`

with a context of `F`

,

then you could unwrap it, feed it to the `f`

function to create a `B`

, and then wrap it again with an `F`

.

You can think of the `map`

function as a producer of `B`

s.

## Contravariant

Contravariant (a.k.a Contravariant Functor) is a type class for type constructors that define a contramap function

```
trait Contravariant[F[_]] {
def contramap[A, B](fa: F[A])(f: B => A): F[B]
}
```

It looks like regular (Covariant) Functor’s map, but the `f`

function is from `B -> A`

instead of `A -> B`

.

You'll be confused if you try to come up with an intuition like the one for `map`

,

because now you have a function from `B -> A`

but the context is `F[A]`

.

if you extract the `A`

from `F[_]`

you will not be able to feed it to the `f`

function.

The best way to understand it is by using an example. The example will use the `Eq`

type class, which is a type constructor.

Let's say we have a case class of Person

```
final case class Person(id: String, name: String, yearOfBirth: Int)
```

and we want to compare between two persons using the id field.

One possible way to do it is to create a new instance of `Eq[Person]`

```
import cats.Eq
import cats.syntax.eq._ // triple equals operator (===)
implicit val eqPerson: Eq[Person] = Eq.instance[Person] { (person1, person2) =>
person1.id === person2.id
}
Person("123", "Samuel Eilenberg", 1913) === Person("123", "Samuel Eilenberg", 1913)
// res0: Boolean = true
```

Another way is to use Contravariant.

Basically we are trying to compare strings, so we can reuse an instance of `Eq[String]`

provided by cats,
and to transform it to an instance of `Eq[Person]`

using the `contramap`

function.

```
import cats.Eq
import cats.syntax.eq._ // triple equals operator (===)
import cats.syntax.contravariant._ // contramap syntax for Eq
implicit val eqPerson: Eq[Person] = Eq[String].contramap[Person](_.id)
Person("123", "Samuel Eilenberg", 1913) === Person("123", "Samuel Eilenberg", 1913)
// res0: Boolean = true
```

That is, if you have a context of `F[A]`

, and you have another type `B`

, that you can extract an `A`

out of it,

then you can get a context of type `F[B]`

using `contramap`

.

For example if you have an instance of `Eq[String]`

and you have another type `Person`

that you can extract

a `String`

out of it (using id), then you will be able to have an instance of `Eq[Person]`

using `contramap`

.

```
def contramap[A, B ](fa: F[A] )(f: B => A ): F[B]
def contramap[String, Person](fa: Eq[String])(f: Person => String): Eq[Person]
```

You can think of the `contramap`

function as a consumer of `B`

s.

## Bifunctor

Bifunctor takes two type parameters instead of one (`F[_, _]`

), and is a (Covariant) Functor on both sides.

```
trait Bifunctor[F[_, _]] {
def bimap[A, B, C, D](fab: F[A, B])(f: A => C, g: B => D): F[C, D]
}
```

It defines a `bimap`

method, which enables to map on both sides.

Example implementation for an instance of a Bifunctor for `Either`

```
import cats.Bifunctor
import cats.syntax.either._
implicit val bifunctorForEither: Bifunctor[Either] = new Bifunctor[Either] {
override def bimap[A, B, C, D](fab: Either[A, B])(f: A => C, g: B => D): Either[C, D] =
fab.fold(a => f(a).asLeft[D], b => g(b).asRight[C])
}
def usageOfBiFunctorForEither(either: Either[Throwable, Int])
(implicit F: Bifunctor[Either]): Either[String, String] =
F.bimap(either)(_.getMessage, _.toString)
usageOfBifunctorForEither(Right(10))
// res0: Either[String,String] = Right(10)
```

## Profunctor

Now we can understand the opening quote.

Profunctors are bifunctors (takes two type parameters instead of one (`P[_, _]`

)

that are contravariant in their first type argument and covariant in their second one.

```
trait Profunctor[P[_, _]] {
def dimap[A, B, C, D](fab: P[A, B])(f: C => A)(g: B => D): P[C, D]
}
```

It defines a `dimap`

method, which enables `contramap`

on the first type parameter and `map`

on the second type parameter.

Example implementation for an instance of a Profunctor for `Function`

```
import cats.arrow.Profunctor
implicit val profunctorForFunction: Profunctor[Function] = new Profunctor[Function] {
override def dimap[A, B, C, D](fab: Function[A, B])(f: C => A)(g: B => D): Function[C, D] =
g compose fab compose f
}
def usageOfProfunctorForFunction(f: Function[Int, Int])
(implicit F: Profunctor[Function]): Function[Person, String] =
F.dimap[Int, Int, Person, String](f)(_.yearOfBirth)(_.toString)
usageOfProfunctorForFunction(_ / 100).apply(Person("123", "Samuel Eilenberg", 1913))
// res0: String = 19
```