Enums and Algebraic Data Types
Prerequisites: Traits, Case classes
Simple enums
enum Color:
case Red, Green, Blue
enum Planet(val massKg: Double, val radiusM: Double):
case Mercury extends Planet(3.303e+23, 2.4397e6)
case Earth extends Planet(5.976e+24, 6.37814e6)
def surfaceGravity: Double = 6.67300e-11 * massKg / (radiusM * radiusM)
@main def enums(): Unit =
println(Color.Green)
println(Color.values.toList)
println(Color.valueOf("Blue").ordinal)
println(f"${Planet.Earth.surfaceGravity}%.2f m/s²")
Green
List(Red, Green, Blue)
2
9.80 m/s²
Enums can have parameters, fields, and methods; values, valueOf, and
ordinal come for free.
Algebraic data types
Enum cases can also carry different data — making an algebraic data type (ADT): a type that is exactly one of several shapes.
enum Payment:
case Card(number: String, expiry: String)
case BankTransfer(iban: String)
case Cash
def describe(p: Payment): String = p match
case Payment.Card(number, _) => s"card ending ${number.takeRight(4)}"
case Payment.BankTransfer(iban) => s"transfer from $iban"
case Payment.Cash => "cash"
@main def payments(): Unit =
val payments = List(
Payment.Card("4111111111111111", "12/27"),
Payment.BankTransfer("GB33BUKB20201555555555"),
Payment.Cash
)
payments.map(describe).foreach(println)
card ending 1111
transfer from GB33BUKB20201555555555
cash
The compiler checks every case
Because the set of cases is closed, the compiler warns when a
match misses one:
enum Light:
case Red, Amber, Green
def next(light: Light): Light = light match
case Light.Red => Light.Green
case Light.Green => Light.Amber
@main def lights(): Unit =
println(next(Light.Red))
-- [E029] Pattern Match Exhaustivity Warning: Main.scala:4:32 ------------------
4 |def next(light: Light): Light = light match
| ^^^^^
| match may not be exhaustive.
|
| It would fail on pattern case: Amber
|
| longer explanation available when compiling with `-explain`
1 warning found
Green
Add a new case to an enum, and the compiler points to every match that
needs updating. This exhaustiveness checking is one of the biggest
benefits of modeling data with ADTs.
Sealed traits
Before Scala 3, ADTs were written as a sealed trait with case classes —
still common, and still useful when cases need their own methods or
hierarchies:
sealed trait Shape
case class Circle(r: Double) extends Shape
case class Square(side: Double) extends Shape
def area(s: Shape): Double = s match
case Circle(r) => math.Pi * r * r
case Square(side) => side * side
@main def sealedTraits(): Unit =
println(area(Square(3)))
9.0
sealed means all subtypes must be in the same file, so the compiler
knows the complete list.