scalaadvanced

Type Class Pattern Implementation

Implement the type class pattern in Scala 3: define, provide instances, and use with extension methods.

scala
// Step 1: Define the type class
trait Eq[T]:
  def eqv(a: T, b: T): Boolean
  def neqv(a: T, b: T): Boolean = !eqv(a, b)

trait Printable[T]:
  def format(value: T): String

trait Combinable[T]:
  def empty: T
  def combine(a: T, b: T): T

// Step 2: Provide instances
object Eq:
  given Eq[Int] with
    def eqv(a: Int, b: Int): Boolean = a == b

  given Eq[String] with
    def eqv(a: String, b: String): Boolean = a == b

  given [A: Eq]: Eq[List[A]] with
    def eqv(a: List[A], b: List[A]): Boolean =
      a.length == b.length && a.zip(b).forall((x, y) => summon[Eq[A]].eqv(x, y))

object Printable:
  given Printable[Int] with
    def format(value: Int): String = value.toString

  given Printable[String] with
    def format(value: String): String = s"\"$value\""

  given Printable[Boolean] with
    def format(value: Boolean): String = if value then "yes" else "no"

object Combinable:
  given Combinable[Int] with
    def empty: Int = 0
    def combine(a: Int, b: Int): Int = a + b

  given Combinable[String] with
    def empty: String = ""
    def combine(a: String, b: String): String = a + b

  given [A]: Combinable[List[A]] with
    def empty: List[A] = Nil
    def combine(a: List[A], b: List[A]): List[A] = a ++ b

// Step 3: Extension methods for ergonomic syntax
extension [T: Eq](a: T)
  def ===(b: T): Boolean = summon[Eq[T]].eqv(a, b)
  def =!=(b: T): Boolean = summon[Eq[T]].neqv(a, b)

extension [T: Printable](value: T)
  def show: String = summon[Printable[T]].format(value)

extension [T: Combinable](a: T)
  def |+|(b: T): T = summon[Combinable[T]].combine(a, b)

// Step 4: Generic algorithms using type classes
def combineAll[T: Combinable](items: List[T]): T =
  val tc = summon[Combinable[T]]
  items.foldLeft(tc.empty)(tc.combine)

def printAll[T: Printable](items: List[T]): Unit =
  items.foreach(item => println(item.show))

// Custom data type with type class instances
case class Money(cents: Long):
  override def toString: String = f"$$${cents / 100.0}%.2f"

given Eq[Money] with
  def eqv(a: Money, b: Money): Boolean = a.cents == b.cents

given Printable[Money] with
  def format(value: Money): String = value.toString

given Combinable[Money] with
  def empty: Money = Money(0)
  def combine(a: Money, b: Money): Money = Money(a.cents + b.cents)

@main def run(): Unit =
  // Eq
  println(1 === 1)        // true
  println(1 =!= 2)        // true
  println("a" === "a")    // true

  // Printable
  println(42.show)         // 42
  println("hello".show)    // "hello"
  println(true.show)       // yes

  // Combinable
  println(1 |+| 2)                    // 3
  println("hello" |+| " world")       // hello world
  println(combineAll(List(1, 2, 3)))  // 6

  // Custom type
  val prices = List(Money(999), Money(1499), Money(2999))
  println(s"Total: ${combineAll(prices)}")
  printAll(prices)

Use Cases

  • Ad-hoc polymorphism without inheritance
  • Generic algorithms with type constraints
  • Extensible behavior for existing types

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.