Traits and mixins

Scala · Practical · 12 min

Traits are Scala's tool for abstraction, code reuse and modular design.

A trait declares abstract members (methods, values, types) and can also provide concrete implementations. Classes and objects extend one class and any number of traits: class Duck extends Animal, Swimmer, Flyer (Scala 3 syntax; with also works).

Use traits to:

  • define interfaces (trait Repository[A] { def find(id: Long): Option[A] }),
  • share behaviour (a Logging trait with a concrete log method),
  • stack modifications (several traits overriding the same method and calling super).

When multiple traits define the same method, Scala uses linearization: the class and its traits are put in a single order (roughly right to left) and super calls the next one in that order. This gives predictable stackable behaviour, unlike the diamond problem in some languages.

Scala 3 traits can take parameters (trait Greeting(val name: String)), which only the class that first mixes it in passes.

Use an abstract class instead when you need Java interoperability with constructors or when a type is clearly a base implementation. Otherwise prefer traits.

Mark a trait sealed when all implementations live in the same file: then the compiler knows every subtype, enabling exhaustive pattern matching (next lesson).

Example

trait Shape:
  def area: Double                       // abstract
  def describe: String = f"${getClass.getSimpleName} with area $area%.2f"

trait Logged extends Shape:
  override def describe: String = "[log] " + super.describe

trait Loud extends Shape:
  override def describe: String = super.describe.toUpperCase

class Circle(r: Double) extends Shape:
  def area = math.Pi * r * r

class Square(s: Double) extends Shape, Loud, Logged:
  def area = s * s

trait Greeting(val greeting: String):
  def greet(n: String) = s"$greeting, $n"
object Formal extends Greeting("Good day")

@main def traitsDemo(): Unit =
  println(Circle(1).describe)      // Circle with area 3.14
  println(Square(2).describe)      // [log] SQUARE WITH AREA 4.00
  println(Formal.greet("Ana"))     // Good day, Ana

Remember

  • Traits mix abstract and concrete members; a class can extend many
  • Linearization orders traits; super calls the next in line
  • Scala 3 traits can take parameters
  • sealed traits enable exhaustive matching

Watch out for

  • Assuming trait order doesn't matter: extends A, B and extends B, A can behave differently.

Check yourself

  1. Square extends Shape, Loud, Logged, both overriding describe and calling super. Which runs first?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.scala-lang.org/scala3/book/domain-modeling-tools.html#traits