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
}