Type Class, a Scala language concept

Last updated

Type classes are one of Scala's most important super-powers: they enable you to add new behaviour to existing classes, without modifying those classes. In many languages, to add a behaviour to a class, you would typically extend it with an interface, and then implement methods against this interface.This, however, does not scale: especially when you have older libraries, you would be forced to make them depend on a new interface, and have to re-build everything.

Type classes are used heavily in Apple's SwiftUI as "extensions" to enable powerful abstraction capabilities.

Key points:

These possibilities are enhanced with syntatic sugar that enables you to develop extremely concise code, and we will demonstrate this here.

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

val combineStrings: Combinable[String] = _ + _

assert(
  combineStrings.combine("A", "B") == "AB",
  "Add new behaviour to Strings, without syntatic sugar"
)

Type classes normally are enhanced with syntatic sugar to save the amount of typing:

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

implicit val combinableString: Combinable[String] = _ + _

implicit class RichCombinable[T](t: T)(implicit combinable: Combinable[T]) {
  def combineWith(other: T): T = combinable.combine(t, other)
}

assert(("A" combineWith "B") == "AB", "Combine using syntatic sugar")

And finally, we can re-use generically implemented behaviour on new classes, by simply declaring a new 'instance' of a type class!

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

implicit val combinableString: Combinable[String] = _ + _

def combineMultiple[T](first: T, rest: T*)(implicit
    combinable: Combinable[T]
): T =
  (first :: rest.toList).reduceLeft[T] {
    case (a, b) => combinable.combine(a, b)
  }

assert(combineMultiple("A", "B", "C") == "ABC")

implicit val combinableInt: Combinable[Int] =
  (a, b) => (a.toString + b.toString).toInt

assert(combineMultiple(1, 2, 3) == 123)

Background

Type classes are a concept inspired by those in Haskell.

Their use massively extends what you can do with Scala, and they are widely used in libraries like 'cats'.

Another worthwhile example of type classes

import Ordering.Implicits._

type CommonType = (Int, String, Option[String])

val a: CommonType = (1, "X", None)

val b: CommonType = (2, "A", Some("B"))

assert(a < b, "We can order tuples using Scala-provided type classes")

To see more of Ordering, go to Ordering.