Implementing Typeclasses

Given instances, extension methods and context bounds allow a concise and natural expression of typeclasses. Typeclasses are just traits with canonical implementations defined by given instances. Here are some examples of standard typeclasses:

Semigroups and monoids:

trait SemiGroup[T] {
  def (x: T) combine (y: T): T
}

trait Monoid[T] extends SemiGroup[T] {
  def unit: T
}

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

given Monoid[String] {
  def (x: String) combine (y: String): String = x.concat(y)
  def unit: String = ""
}

given Monoid[Int] {
  def (x: Int) combine (y: Int): Int = x + y
  def unit: Int = 0
}

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

Functors and monads:

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

trait Monad[F[_]] extends Functor[F] {
  def [A, B](x: F[A]).flatMap(f: A => F[B]): F[B]
  def [A, B](x: F[A]).map(f: A => B) = x.flatMap(f `andThen` pure)

  def pure[A](x: A): F[A]
}

given listMonad as Monad[List] {
  def [A, B](xs: List[A]).flatMap(f: A => List[B]): List[B] =
    xs.flatMap(f)
  def pure[A](x: A): List[A] =
    List(x)
}

given readerMonad[Ctx] as Monad[[X] =>> Ctx => X] {
  def [A, B](r: Ctx => A).flatMap(f: A => Ctx => B): Ctx => B =
    ctx => f(r(ctx))(ctx)
  def pure[A](x: A): Ctx => A =
    ctx => x
}