(15) Enums
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.
image source: 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.
We can use Enum – An enumeration refers to a group of named constants – a data type that contains only a finite set of named values.
image source: https://commons.wikimedia.org/wiki/File:CPT-OOP-objects_and_classes.svg
Enumeration – 1/5: Case Object
Case Object
A case object is like an object, but a case class has more features than a regular class. 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 // val res3: Array[Direction] = Array(Right, Up, Left, Down)
}
val r = Direction.Right
val u = r.leftTurn // u = Up
val v = (u.dx, u.dy) // v = (1, 0)
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
Scala enumerations are not compatible with Java; that is to say, Java code won’t use an enumeration declared in Scala, regardless of which approach we chose to encode them.
Scala 3 solves these problems by unifying enums under a new syntax, which we can optionally make compatible with Java Enums:
object CurrencyADT(name: String, iso: String) extends java.lang.Enum {
case EUR("Euro", "EUR")
case USD("United States Dollar", "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
Summary of #values
#values
is an immutable array in the companion object of an enum that contains all enum values- Only simple cases have ordinal numbers and show up in values. Parameterized cases do not.
X Enumeration – 1: Case Object
X Enumeration – 2: Case Class
X Enumeration – 3: #enum key word – Type 1 – Parameterized in the Enum Block
✓ Enumeration – 3: #enum key word – Type 2 – Basic Enum
✓ Enumeration – 3: #enum key word – Type 3 – Parameterized Enums
✓ Enumeration – 4: Extends Enumeration