(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.

Background reference: https://upload.wikimedia.org/wikipedia/commons/9/98/CPT-OOP-objects_andclasses-_attmeth.svg

file

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

file

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.

Leave a Reply

Your email address will not be published. Required fields are marked *