Type classes

More than just strategy pattern

Mateusz Kubuszok

About me

Agenda

  • what is is a strategy

  • how to get from strategy pattern to type class pattern

  • code generation

  • some real world example

Strategy pattern

trait DiscountStrategy {
  def apply(price: Int): Int
}

def applyDiscounts(
    item: Item, // case class Item(name: String, price: Int)
    quantity: Int,
    discount: Option[DiscountStrategy]
): (Int, Int) = {
  val price = item.price * quantity
  val discounted = discount.fold(price)(_.apply(price))
  (price, discounted)
}
  • algorithm extracts dynamic logic to an interface

  • we provide instance of that interface as an argument

Strategy with generics

// in Java it would be:
// interface Show<A> { ... }
trait Show[A] {
  def show(a: A): String
}

def show[A](value: A)(show: Show[A]): String =
  show.show(value)
class Example(val fiels: String)

val example: Example = new Example("test")

val showExample: Show[Example] =
  // Single Abstract Method syntax:
  // creates new Show[Example] { ... }
  // as if it was a function
  value => s"Example(${value.field})"

show(example)(showExample)

Implicits and extension methods

trait Show[A] {
  def show(a: A): String
}

extension [A](value: A)
  def show(using show: Show[A]): String = show.show(value)
class Example(val fiels: String)
val example: Example = new Example("test")

given showExample: Show[Example] =
  value => s"Example(${value.field})"

example.show(using showExample)
example.show // showExample is passed for us
given Show[Example] =
  value => s"Example(${value.field})"

example.show // anonymous Show[Example] is passed for us

Type Class

  • strategy pattern

  • with type parameters (generics)

  • where the algorithm often run as an extension method

  • and the strategy is passed with type-based DI

Generating implementation from smaller blocks

given Show[String] = str => '"' + str + '"'
given Show[Int] = int => int.toString

given [A](using A: Show[A]): Show[Array[A]] = arr =>
  s"Array(${arr.map(_.show).mkString(", ")})"

given [A, B](using A: Show[A], B: Show[B]): Show[(A, B)] = {
  case (a, b) =>
    s"Tuple2(${a.show}, ${b.show})"
}

Type class derivation

Generating a type class for some type using:

  • type classes for its components (record’s fields, subtypes)

  • some logic how to combine/dispatch to components' type classes

Examples of type-class-based solutions

  • JSON serialization

  • config parsing

  • type mapping

Summary

  • (on JVM) type class can be understood as a strategy patter

  • with a (type-based) dependency injection

  • and a code generation

  • slides and examples available on my GitHub profile GitHub.com/MateuszKubuszok

Questions?

Thank You!