# Overloaded type families

## Overloading

Overloading is to give the same name to different implementations.
In Haskell, type classes enable overloading of terms. For example, the two
functions `map`

and `(.)`

are generalized by the concept of “functors”:

```
class Functor f where
fmap :: (a -> b) -> (f a -> f b)
instance Functor [] where
fmap :: (a -> b) -> ([a] -> [b])
fmap = map
instance Functor ((->) r) where
fmap :: (a -> b) -> ((r -> a) -> (r -> b))
fmap = (.)
```

## What about type families?

We can also define a type-level `fmap`

simply as:

`type family FMap (m :: a -> b) (x :: f a) :: f b`

(Enable the extensions `TypeFamilies`

and also
`TypeInType`

.)

“Type class instances of `Functor`

” become simply “type family instances of
`FMap`

”. For lists:

```
-- FMap :: (a -> b) -> ([a] -> [b])
type instance FMap m '[] = '[]
type instance FMap m (v ': vs) = m v ': FMap m vs
```

And `FMap`

for type constructors (`r -> a`

) can be defined using
`Data.Functor.Compose`

.
Compare this to the instance `Functor ((->) r)`

above:

```
-- FMap :: (a -> b) -> ((r -> a) -> (r -> b))
type instance FMap m n = Compose m n
```

Whereas at the term level, functions can be defined by pattern-matching
on their arguments, at the type level, type families can be defined by
pattern-matching not only on their arguments but also their kinds.
Notice that in the last type family instance above, we actually don’t
inspect `m`

and `n`

, but the type family will check whether the kind
of `n`

is an arrow `r -> a`

in order to use that instance.

We can similarly write a lazy instance of `FMap`

for `Proxy`

:

```
-- FMap :: (a -> b) -> Proxy a -> Proxy b
type instance FMap m p = 'Proxy
```

The resulting mechanism resembles type classes, but the ergonomics are not
quite the same. One difference is that there is no type class constraint on
`FMap`

, because constraints are not a thing at that level. Whereas wrongly
applying `fmap`

may cause a “missing instance” error, incorrect usage of `FMap`

just gives us a stuck term, and we may somehow have to look for our mistake in
a huge type-level expression dumped by the compiler. That gap might be filled by
recent work on “constrained type families”.

Nevertheless, it’s quite cool that overloading at the type level Just Works™.

Here, this is a simple type family to keep it simple, but it applies to
first-class families too. Thanks to *isovector* for pointing
out
this feature to me!