Day 3 of 5
⏱ ~60 minutes
Scala in 5 Days — Day 3

Traits, Type Classes & Implicits

Scala's trait system enables flexible code composition. Type classes provide ad-hoc polymorphism. Given/using (Scala 3) and implicits (Scala 2) enable powerful context-passing patterns used throughout the ecosystem.

Traits: Scala's Interface+Mixin

Traits are like Java interfaces but can contain concrete implementations. A class can extend multiple traits (no diamond problem — linearization resolves conflicts). Traits can have abstract and concrete methods, fields, and even state. Self-type annotations (this: OtherTrait =>) declare that a trait requires another trait to be mixed in, enabling dependency injection at the type level.

Type Classes in Scala

A type class is a trait with a type parameter: 'trait Serializable[A] { def serialize(a: A): String }'. Instances are provided as given/implicit values: 'given Serializable[Int] = new Serializable[Int] { ... }'. Functions using type classes take an implicit/using parameter: 'def print[A](a: A)(using s: Serializable[A]) = s.serialize(a)'. The compiler automatically finds and passes the right instance.

Extension Methods and Scala 3

Scala 3 added extension methods as a first-class feature: 'extension (n: Int) def isEven: Boolean = n % 2 == 0'. This adds methods to existing types without subclassing. Scala 3 also replaced implicit with more explicit given/using syntax, making context passing clearer. Union types (Int | String), intersection types (A & B), and opaque type aliases are powerful new type system features.

scala
// Type class pattern (Scala 3)
trait Show[A]:
  def show(a: A): String

given Show[Int] with
  def show(n: Int) = s'Int($n)'

given Show[String] with
  def show(s: String) = s'"$s"'

given [A: Show] => Show[List[A]] with
  def show(xs: List[A]) =
    xs.map(summon[Show[A]].show).mkString('[', ', ', ']')

def display[A](a: A)(using s: Show[A]): Unit =
  println(s.show(a))

display(42)               // Int(42)
display(List(1, 2, 3))    // [Int(1), Int(2), Int(3)]

// Extension methods (Scala 3)
extension (s: String)
  def isPalindrome: Boolean = s == s.reverse
  def wordCount: Int = s.trim.split('\\s+').length

println('racecar'.isPalindrome)          // true
println('Hello world foo'.wordCount)    // 3
💡
Use summon[TC[A]] in Scala 3 (the equivalent of implicitly in Scala 2) to retrieve an implicit/given instance explicitly. This is clearer than relying on the compiler to inject it invisibly.
📝 Day 3 Exercise
Build a JSON Encoder Type Class
  1. Define a trait JsonEncodable[A] with a method toJson(a: A): String
  2. Provide given instances for Int, Double, String, Boolean, and List[A: JsonEncodable]
  3. Define a case class Person(name: String, age: Int) and provide its instance
  4. Write a function encode[A: JsonEncodable](a: A): String that uses the type class
  5. Test encoding a List[Person] — the compiler should find all instances automatically

Day 3 Summary

  • Traits support concrete methods and fields, enabling rich mixin composition
  • Type classes provide ad-hoc polymorphism without inheritance
  • Scala 3 given/using replaces Scala 2 implicit for clearer context passing
  • Extension methods add methods to existing types without subclassing
  • summon[TC[A]] retrieves a given/implicit instance explicitly
Challenge

Implement a type-safe CSV encoder using type classes in Scala 3. Support: Int, Double, String, Boolean, Option[A], and case classes (using a macro or manual derivation). Write tests encoding a List[Person] to a valid CSV string.

Finished this lesson?