Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Type Classes

A type class is the functional equivalent of polymorphism in the object-oriented world. And it is resolved in compile-time by importing context givens

The Object-Oriented Way

  trait Show:
    def show: String

  case class Person(name: String, age: Int) extends Show:
    override def show = s"$name with age $age"

  val john = Person("John", 28 )
  val showJohn = john.show
  • available only for the types we extend
  • provide only one override implementation

The Pattern Matching Way

def show(value: Any): String = value match 
  case Person(name, age) => s"$name with age $age"
  case _ => throw new IllegalArgumentException("not supported")
  • lose type safety
  • extends match for every pattern
  • has one case implementation

Type Classes

Create a type class has the following steps

  1. Type Class definition
  2. Type Class instances
  3. the API
  4. Extension methods

Type Class Definition

trait Shower[T] {
	def show(value: T): String
}

Type Class Instances

given userShower: Shower[Person] with
  override def show(value: Person) =
    val Person(name, age) = value
    s"$name with age $age"}

val john = Person("John", 28)
val showJohn = userShower.show(john)

API

object Shower {
  def show[T](value: T)(using shower: Shower[T]): String =
    shower.show(value)

  def apply[T](using shower: Shower[T]): Shower[T] = shower
}

val showJohn = Shower.show(john)

Extension method


object ShowSyntax:
  extension [T](value: T)
    def show(using shower: Shower[T]): String = shower.show(value)

import ShowSyntax.*
val showJohn = john.show
  • can define showers for other types
  • multiple shower for the same type

Example Dog ad Cat

In Scala 3, the mechanism for defining and using type classes has been significantly revamped with the introduction of given and using clauses, replacing the older implicit keyword. This change aims to make the definition and usage of type classes more explicit and readable, addressing some of the common criticisms of Scala's implicit system.

Defining Type Classes with given

To define a type class instance with Scala 3, you use the given keyword. This replaces the implicit val or implicit object definitions used in Scala 2.

Example: Defining a SoundMaker type class and its instances.

trait SoundMaker[T]:
  def makeSound(value: T): Unit

// Define instances of the SoundMaker type class
given SoundMaker[Dog] with
  def makeSound(dog: Dog): Unit = println("Woof")


given SoundMaker[Cat] with
  def makeSound(cat: Cat): Unit = println("Meow")

Here, given declarations define how Dog and Cat types fulfill the SoundMaker contract. These instances are globally available and automatically used wherever a SoundMaker[T] is required.

Using Type Classes with using

To use a type class instance, Scala 3 introduces the using clause. This explicitly specifies that a function requires a type class instance for its operation, making the function's dependencies clear.

Example: Using the SoundMaker type class to implement a polymorphic playSound function.

def playSound[T](value: T)(using maker: SoundMaker[T]): Unit = maker.makeSound(value)

The using clause in the function signature tells the compiler to search for an implicit SoundMaker[T] instance for whatever type T is passed into the function. This search is based on the instances defined with given.

Putting It All Together

class Dog(val name: String)
class Cat(val name: String)

// Assuming the given instances and the playSound method are defined as above

val myDog = Dog("Rex")
val myCat = Cat("Whiskers")

playSound(myDog) // Outputs: Woof
playSound(myCat) // Outputs: Meow

Type Classes Exercises

Exercise: Json Converter