This post assumes prior knowledge of - Contravariant - Bifunctor

## Why

We've seen how types of kind `* -> *`

can have instances for `Functor`

or `Contravariant`

, depending on the position of the type argument. We have also seen that types of kind `* -> * -> *`

can have `Bifunctor`

instances. These types are morally `Functor`

in both type arguments. We're left with one very common type which we can't map both arguments of: `a -> b`

. It does have a `Functor`

instance for `b`

, but the `a`

is morally `Contravariant`

(so it can't have a `Bifunctor`

instance). This is where `Profunctor`

s come in.

Here's a list of a few common types with the instances they allow:

Type | `Functor` |
`Bifunctor` |
`Contravariant` |
`Profunctor` |
---|---|---|---|---|

`Maybe a` |
✓ | |||

`[a]` |
✓ | |||

`Either a b` |
✓ | ✓ | ||

`(a,b)` |
✓ | ✓ | ||

`Const a b` |
✓ | ✓ | ||

`Predicate a` |
✓ | |||

`a -> b` |
✓ | ✓ |

Although there are some exceptions, you will usually see `Contravariant`

or `Profunctor`

instances over function types. `Predicate`

itself is a newtype over `a -> Bool`

, and so are most types with these instances.

Let's take a closer look at `a -> b`

. We can easily map over the `b`

, but what about the `a`

? For example, given `showInt :: Int -> String`

, what do we need to convert this function to `showBool :: Bool -> String`

:

```
showInt :: Int -> String
showInt = show
showBool :: Bool -> String
= _help showBool b
```

We would have access to: - `showInt :: Int -> String`

- `b :: Bool`

and we want to use `showInt`

, so we would need a way to pass `b`

to it, which means we'd need a function `f :: Bool -> Int`

and then `_help`

would become `showInt (f b)`

.

But if we take a step back, in order to go from `Int -> String`

to `Bool -> String`

, we need `Bool -> Int`

, which is exactly the `Contravariant`

way of mapping types.

*Exercise 1*: Implement a `mapInput`

function like:

`mapInput :: (input -> out) -> (newInput -> input) -> (newInput -> out)`

Extra credit: try a pointfree implementation as `mapInput = _`

.

*Exercise 2*: Try to guess how the `Profunctor`

class looks like. Look at `Functor`

, `Contravariant`

, and `Bifunctor`

for inspiration.

`class Profunctor p where`

*Exercise 3*: Implement an instance for `->`

for your `Profunctor`

class.

`instance Profunctor (->) where`

## How

Unlike `Functor`

, `Contravariant`

, and `Bifunctor`

, the `Profunctor`

class is not in `base`

/`Prelude`

. You will need to bring in a package like `profunctors`

to access it.

```
class Profunctor p where
{-# MINIMAL dimap | lmap, rmap #-}
dimap :: (c -> a) -> (b -> d) -> p a b -> p c d
lmap :: (c -> a) -> p a b -> p c b
rmap :: (b -> c) -> p a b -> p a c
```

`dimap`

takes two functions and is able to map both arguments in a type of kind `* -> * -> *`

. `lmap`

is like `mapInput`

. `second`

is always the same thing as `fmap`

.

*Exercise 4*: implement `dimap`

in terms of `lmap`

and `rmap`

.

*Exercise 5*: implement `lmap`

and `rmap`

in terms of `dimap`

.

*Exercise 6*: implement the `Profunctor`

instance for `->`

:

```
instance Profunctor (->) where
-- your pick: dimap or lmap and rmap
```

*Exercise 7*: (hard) implement the `Profunctor`

instance for:

```
data Sum f g a b
= L (f a b)
| R (g a b)
instance (Profunctor f, Profunctor g) => Profunctor (Sum f g) where
```

*Exercise 8*: (hard) implement the `Profunctor`

instance for:

```
newtype Product f g a b = Product (f a b, g a b)
instance (Profunctor f, Profunctor g) => Profunctor (Product f g) where
```