Typeclasses define shared behavior in an abstract way, allowing us to use the same function on different types. We can use typeclass constraints to specify that a type parameter can be any type that implements the class.

> [!note]
> Typeclasses are very similar to interfaces in other languages, but with some differences.

# Defining a Typeclass
Class definitions have several components:
- Class constraints
- Class name
- Type parameters
- Functional dependencies
- List of functions

## Class constraints
```haskell
class Y a <= X a
```
In order for the implementation of `X` for `a` to be valid, `a` must also implement `Y`.

## Class name
```haskell
class X a b c
class Y a <= X a
class X
```
Are all valid definitions of a class named `X`.

## Type parameters
```haskell
class X a
```
Classes can have zero, one, or more type parameters. A single type parameter is likely the most common, which signifies "the interface `X` for some `a`."

Still common, but less so, are multiple type parameters:
```haskell
class X a b c
```

Here the interface applies to "any combination of a, b, and c."

## Functional dependencies
Functional dependencies allow us to embed "implies"-ness to the class definition, for example in:
```haskell
class X a b | a -> b
```
We're saying that if we're calling a function in `X` and we know `a` then we also know `b`. There can't be multiple implementations for the same type `a` and different `b`s.

We could also say "if we know either a or b, we know the other"
```haskell
class X a b | a -> b, b -> a
```

"if we know a and b, then we know c"
```haskell
class X a b c | a b -> c
```

"if we know a or b, then we know c"
```haskell
class X a b c | a -> c, b -> c
```

These most often are used in classes with [[Row Types]] as parameters.

This type class is invalid:
```haskell
class X a where
  addFoo :: Record a -> Record (foo :: Unit | a)

instance X () where
  addFoo {} = {foo: unit}
```

```text
[ERROR 1/1 InvalidInstanceHead]

      v
   1  instance X () where
   2    addFoo {} = {foo: unit}
                              ^

  Type class instance head is invalid due to use of type
    ()
  All types appearing in instance declarations must be of the form T a_1 .. a_n, where each type a_i is of the same form, unless the type is fully determined by other type class arguments via functional dependencies.
  in type class instance
    X ()
```

This happens because a row type like `()` is structural and not strictly _unique_ in the same way normal types are.

This is where [[RowList]] comes in, adding the ability to declare type classes on normal types, which translate to row types:

```haskell
import Prim.RowList as RowList

class (RowToList a rl) <= X a rl where
  addFoo :: Record a -> Record (foo :: Unit | a)

instance (RowToList a rl) => X a RowList.Nil where
  addFoo {} = {foo: unit}
```

Now our typeclass is valid for all rows whose RowList representation is `RowList.Nil`. (In this specific case this can only be empty row `()`)

If we added the functional dependency `rl -> a`, then we can actually use row types in the instance:

```haskell
import Prim.RowList as RowList

class (RowToList a rl) <= X a rl | rl -> a where
  addFoo :: Record a -> Record (foo :: Unit | a)

instance X () RowList.Nil where
  addFoo {} = {foo: unit}
```

Because we've told the type system how to unambiguously choose the correct class instance for a given row type.

