Background
Classes are essentially bundles of functions operating on some common values represented as fields. They are a very useful abstraction since they allow the encapsulation of data.
Background reference: https://upload.wikimedia.org/wikipedia/commons/9/98/CPT-OOP-objects_andclasses-_attmeth.svg

But sometimes, we just need to compose and decompose pure data without any
associated functions.
A simple enumeration has a finite set of named values. Scala 3 enums can also define parameterized cases, forming algebraic data types whose possible values need not be finite.
Background reference: https://commons.wikimedia.org/wiki/File:CPT-OOP-objects_and_classes.svg
Enumeration – 1/5: Case Object
Case Object
A case object is a singleton object with generated case-style support. A case object has more features than a regular one. Its features include:
Case Object vs Object
- It’s serializable
- It has a default hashCode implementation
- It has an improved toString implementation
Usage
Because of these features, case objects are primarily used in two places (instead of regular objects):
- When creating enumerations
- When creating containers for “messages” that you want to pass between other objects (such as with the Akka actors library)
Code Example
sealed trait DayOfWeek
case object Sunday extends DayOfWeek
case object Monday extends DayOfWeek
case object Tuesday extends DayOfWeek
case object Wednesday extends DayOfWeek
case object Thursday extends DayOfWeek
case object Friday extends DayOfWeek
case object Saturday extends DayOfWeek
Sunday // val res0: Sunday.type = Sunday
Monday // val res1: Monday.type = Monday
s"Today is $Saturday" // val res2: String = Today is Saturday
// Error: does not have #values
// DayOfWeek.values
Enumeration – 2/5: case class
Example of Algebraic Data Types (ADTs)
trait Expr
object Expr {
case class Var(s: String) extends Expr
case class Number(n: Int) extends Expr
case class Sum(e1: Expr, e2: Expr) extends Expr
case class Prod(e1: Expr, e2: Expr) extends Expr
}
Expr.Number(10) // val res0: Expr.Number = Number(10)
// Error: does not have #values
// Expr.values
Enumeration – 3/5: #enum key word
Advantage
Easy to write
Type 1 – Parameterized in the Enum Block
enum Expr {
case Var(s: String)
case Number(n: Int)
case Sum(e1: Expr, e2: Expr)
case Prod(e1: Expr, e2: Expr)
}
// Error: does not have #values
// Expr.values
This enum is equivalent to the case class hierarchy on the previous slide but is shorter since it avoids the repetitive notation of
class ... extends Expr
Type 2 – Basic Enum
enum Color {
case Red
case Green
case Blue
}
Color.Red // val res1: Color = Red
Color.values // val res2: Array[Color] = Array(Red, Green, Blue)
Type 3 – Parameterized Enums
Enumerations can take parameters and can define methods.
enum Direction(val dx: Int, val dy: Int) {
case Right extends Direction(1, 0)
case Up extends Direction(0, 1)
case Left extends Direction(-1, 0)
case Down extends Direction(0, -1)
def leftTurn = Direction.values((ordinal + 1) % 4)
}
Direction.values // Array(Right, Up, Left, Down)
val r = Direction.Right
val u = r.leftTurn // u = Up
val v = (u.dx, u.dy) // v = (0, 1)
Enumeration – 4/5: Extends Enumeration
Code Example
object Weekday extends Enumeration {
val Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday = Value
}
Weekday.Monday // val res0: Weekday.Value = Monday
Weekday.withName("Monday") // val res1: Weekday.Value = Monday
Weekday.values
// val res2: Weekday.ValueSet = Weekday.ValueSet(Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday)
Provide a human-readable value
object Weekday2 extends Enumeration {
val Monday = Value("Mo.")
val Tuesday = Value("Tu.")
val Wednesday = Value("We.")
val Thursday = Value("Th.")
val Friday = Value("Fr.")
val Saturday = Value("Sa.")
val Sunday = Value("Su.")
}
Weekday2.Monday // val res2: Weekday2.Value = Mo.
Weekday2.Monday.toString // val res3: String = Mo.
Weekday.withName("Monday") // val res1: Weekday.Value = Monday
Weekday2.values
// val res4: Weekday2.ValueSet = Weekday2.ValueSet(Mo., Tu., We., Th., Fr., Sa., Su.)
Issues
Due to type erasure, all enums have the same type at runtime.
// --------------------
// Issue
object CurrencyEnum extends Enumeration {
type Currency = Value
val GBP = Value(1, "GBP")
val EUR = Value
}
object InternetCodes extends Enumeration {
type CountryCode = Value
val EU, DE, CO = Value
}
import InternetCodes._
import CurrencyEnum._
// Error
object Methods {
def method(currency: Currency): Currency = currency
def method(countryCode: CountryCode): CountryCode = countryCode
}
//|Double definition:
// |def method(currency: CurrencyEnum.Currency): CurrencyEnum.Currency in object Methods at line 2 and
// |def method(countryCode: InternetCodes.CountryCode): InternetCodes.CountryCode in object Methods at line 3
Enumeration – 5/5: Java Enum
Being usable from Java is different from being a Java enum. A Scala 3 enum can explicitly extend java.lang.Enum[ItsOwnType] for Java-enum compatibility; other Scala encodings are not automatically Java enums.
Scala 3 solves these problems by unifying enums under a new syntax, which we can optionally make compatible with Java Enums:
enum CurrencyADT extends java.lang.Enum[CurrencyADT] {
case EUR, USD
}
Pattern Matching
Pattern Matching Example:
enum DayOfWeek {
case Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday
}
import DayOfWeek.*
def isWeekend(day: DayOfWeek) = day match
case Saturday | Sunday => true
case _ => false
isWeekend(Monday) // val res0: Boolean = false
isWeekend(Saturday) // val res1: Boolean = true
Enumeration helpers: know which encoding you use
A Scala 3 enum with only singleton cases has a companion values returning an Array; arrays are mutable. A singleton case can pass fixed arguments to its enum constructor and still appear in values, as Direction does above. A case that itself takes arguments, such as Number(n: Int), represents many instances and prevents automatic enumeration of all values. Its instances still have a case ordinal; ordinals identify cases, not arbitrary payload values.
Legacy scala.Enumeration.values instead returns a ValueSet. Hand-written sealed case-object/class families have no automatically generated values.
Takeaway
Use a simple enum for named choices, parameterized cases for structured data, and explicit Java-enum inheritance when that interoperability is required. Keep Scala 2 and Scala 3 encodings separate.
Reference: Scala 3 enumerations.