A *type class* is an abstract, parameterized type that lets you add new behavior to any closed data type without using sub-typing. This can be useful in multiple use-cases, for example:

- expressing how a type you don’t own (from the standard or 3rd-party library) conforms to such behavior
- expressing such a behavior for multiple types without involving sub-typing relationships (one
`extends`

another) between those types (see: ad hoc polymorphism for instance)

Therefore in Scala 3, *type classes* are just *traits* with one or more parameters whose implementations are not defined through the `extends`

keyword, but by **given instances**.
Here are some examples of common type classes:

### Semigroups and monoids

Here’s the `Monoid`

type class definition:

```
trait SemiGroup[T]:
extension (x: T) def combine (y: T): T
trait Monoid[T] extends SemiGroup[T]:
def unit: T
```

An implementation of this `Monoid`

type class for the type `String`

can be the following:

```
given Monoid[String] with
extension (x: String) def combine (y: String): String = x.concat(y)
def unit: String = ""
```

Whereas for the type `Int`

one could write the following:

```
given Monoid[Int] with
extension (x: Int) def combine (y: Int): Int = x + y
def unit: Int = 0
```

This monoid can now be used as *context bound* in the following `combineAll`

method:

```
def combineAll[T: Monoid](xs: List[T]): T =
xs.foldLeft(summon[Monoid[T]].unit)(_.combine(_))
```

To get rid of the `summon[...]`

we can define a `Monoid`

object as follows:

```
object Monoid:
def apply[T](using m: Monoid[T]) = m
```

Which would allow to re-write the `combineAll`

method this way:

```
def combineAll[T: Monoid](xs: List[T]): T =
xs.foldLeft(Monoid[T].unit)(_.combine(_))
```

### Functors

A `Functor`

for a type provides the ability for its values to be “mapped over”, i.e. apply a function that transforms inside a value while remembering its shape. For example, to modify every element of a collection without dropping or adding elements.
We can represent all types that can be “mapped over” with `F`

. It’s a type constructor: the type of its values becomes concrete when provided a type argument.
Therefore we write it `F[_]`

, hinting that the type `F`

takes another type as argument.
The definition of a generic `Functor`

would thus be written as:

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

Which could read as follows: “A `Functor`

for the type constructor `F[_]`

represents the ability to transform `F[A]`

to `F[B]`

through the application of function `f`

with type `A => B`

”. We call the `Functor`

definition here a *type class*.
This way, we could define an instance of `Functor`

for the `List`

type:

```
given Functor[List] with
def map[A, B](x: List[A], f: A => B): List[B] =
x.map(f) // List already has a `map` method
```

With this `given`

instance in scope, everywhere a `Functor`

is expected, the compiler will accept a `List`

to be used.

For instance, we may write such a testing method:

```
def assertTransformation[F[_]: Functor, A, B](expected: F[B], original: F[A], mapping: A => B): Unit =
assert(expected == summon[Functor[F]].map(original, mapping))
```

And use it this way, for example:

```
assertTransformation(List("a1", "b1"), List("a", "b"), elt => s"${elt}1")
```

That’s a first step, but in practice we probably would like the `map`

function to be a method directly accessible on the type `F`

. So that we can call `map`

directly on instances of `F`

, and get rid of the `summon[Functor[F]]`

part.
As in the previous example of Monoids, `extension`

methods help achieving that. Let’s re-define the `Functor`

type class with extension methods.

```
trait Functor[F[_]]:
extension [A](x: F[A])
def map[B](f: A => B): F[B]
```

The instance of `Functor`

for `List`

now becomes:

```
given Functor[List] with
extension [A](xs: List[A])
def map[B](f: A => B): List[B] =
xs.map(f) // List already has a `map` method
```

It simplifies the `assertTransformation`

method:

```
def assertTransformation[F[_]: Functor, A, B](expected: F[B], original: F[A], mapping: A => B): Unit =
assert(expected == original.map(mapping))
```

The `map`

method is now directly used on `original`

. It is available as an extension method
since `original`

’s type is `F[A]`

and a given instance for `Functor[F[A]]`

which defines `map`

is in scope.

### Monads

Applying `map`

in `Functor[List]`

to a mapping function of type `A => B`

results in a `List[B]`

. So applying it to a mapping function of type `A => List[B]`

results in a `List[List[B]]`

. To avoid managing lists of lists, we may want to “flatten” the values in a single list.

That’s where `Monad`

comes in. A `Monad`

for type `F[_]`

is a `Functor[F]`

with two more operations:

`flatMap`

, which turns an`F[A]`

into an`F[B]`

when given a function of type`A => F[B]`

,`pure`

, which creates an`F[A]`

from a single value`A`

.

Here is the translation of this definition in Scala 3:

```
trait Monad[F[_]] extends Functor[F]:
/** The unit value for a monad */
def pure[A](x: A): F[A]
extension [A](x: F[A])
/** The fundamental composition operation */
def flatMap[B](f: A => F[B]): F[B]
/** The `map` operation can now be defined in terms of `flatMap` */
def map[B](f: A => B) = x.flatMap(f.andThen(pure))
end Monad
```

#### List

A `List`

can be turned into a monad via this `given`

instance:

```
given listMonad: Monad[List] with
def pure[A](x: A): List[A] =
List(x)
extension [A](xs: List[A])
def flatMap[B](f: A => List[B]): List[B] =
xs.flatMap(f) // rely on the existing `flatMap` method of `List`
```

Since `Monad`

is a subtype of `Functor`

, `List`

is also a functor. The Functor’s `map`

operation is already provided by the `Monad`

trait, so the instance does not need to define
it explicitly.

#### Option

`Option`

is an other type having the same kind of behaviour:

```
given optionMonad: Monad[Option] with
def pure[A](x: A): Option[A] =
Option(x)
extension [A](xo: Option[A])
def flatMap[B](f: A => Option[B]): Option[B] = xo match
case Some(x) => f(x)
case None => None
```

#### Reader

Another example of a `Monad`

is the *Reader* Monad, which acts on functions instead of
data types like `List`

or `Option`

. It can be used to combine multiple functions
that all need the same parameter. For instance multiple functions needing access to some configuration, context, environment variables, etc.

Let’s define a `Config`

type, and two functions using it:

```
trait Config
// ...
def compute(i: Int)(config: Config): String = ???
def show(str: String)(config: Config): Unit = ???
```

We may want to combine `compute`

and `show`

into a single function, accepting a `Config`

as parameter, and showing the result of the computation, and we’d like to use
a monad to avoid passing the parameter explicitly multiple times. So postulating
the right `flatMap`

operation, we could write:

```
def computeAndShow(i: Int): Config => Unit = compute(i).flatMap(show)
```

instead of

```
show(compute(i)(config))(config)
```

Let’s define this m then. First, we are going to define a type named `ConfigDependent`

representing a function that when passed a `Config`

produces a `Result`

.

```
type ConfigDependent[Result] = Config => Result
```

The monad instance will look like this:

```
given configDependentMonad: Monad[ConfigDependent] with
def pure[A](x: A): ConfigDependent[A] =
config => x
extension [A](x: ConfigDependent[A])
def flatMap[B](f: A => ConfigDependent[B]): ConfigDependent[B] =
config => f(x(config))(config)
end configDependentMonad
```

The type `ConfigDependent`

can be written using type lambdas:

```
type ConfigDependent = [Result] =>> Config => Result
```

Using this syntax would turn the previous `configDependentMonad`

into:

```
given configDependentMonad: Monad[[Result] =>> Config => Result] with
def pure[A](x: A): Config => A =
config => x
extension [A](x: Config => A)
def flatMap[B](f: A => Config => B): Config => B =
config => f(x(config))(config)
end configDependentMonad
```

It is likely that we would like to use this pattern with other kinds of environments than our `Config`

trait. The Reader monad allows us to abstract away `Config`

as a type *parameter*, named `Ctx`

in the following definition:

```
given readerMonad[Ctx]: Monad[[X] =>> Ctx => X] with
def pure[A](x: A): Ctx => A =
ctx => x
extension [A](x: Ctx => A)
def flatMap[B](f: A => Ctx => B): Ctx => B =
ctx => f(x(ctx))(ctx)
end readerMonad
```

### Summary

The definition of a *type class* is expressed with a parameterised type with abstract members, such as a `trait`

.
The main difference between subtype polymorphism and ad-hoc polymorphism with *type classes* is how the definition of the *type class* is implemented, in relation to the type it acts upon.
In the case of a *type class*, its implementation for a concrete type is expressed through a `given`

instance definition, which is supplied as an implicit argument alongside the value it acts upon. With subtype polymorphism, the implementation is mixed into the parents of a class, and only a single term is required to perform a polymorphic operation. The type class solution
takes more effort to set up, but is more extensible: Adding a new interface to a
class requires changing the source code of that class. But contrast, instances for type classes can be defined anywhere.

To conclude, we have seen that traits and given instances, combined with other constructs like extension methods, context bounds and type lambdas allow a concise and natural expression of *type classes*.