Enums And Algebraic Data Types (ADT)

By Nadim Bahadoor | Last updated: February 17, 2020 at 9:30 am

Overview

The previous tutorial was perhaps a good introduction to represent a given enumeration - that is, a distinct set of values that represent some real-world data type or event. As we've already noted, these values are typically unique, as well as having some predefined or assumed ordering. For instance, you would perhaps want to model the given day of the week, or months, in a year. And, yes, we've heard the complaints that in, say, Java, or C#.NET, you can easily model such a data structure or type using the enum keyword. But, Scala does not provide such an enum keyword, and instead you have to rely on the Enumeration class. As a matter of fact, the following examples from the Scala 2.13 documentation further illustrate representing finite or distinct set of values using an Enumeration.


// Define a new enumeration with a type alias and work with the full set of enumerated values
object WeekDay extends Enumeration {
  type WeekDay = Value
  val Mon, Tue, Wed, Thu, Fri, Sat, Sun = Value
}
import WeekDay._

def isWorkingDay(d: WeekDay) = ! (d == Sat || d == Sun)

WeekDay.values filter isWorkingDay foreach println
// output:
// Mon
// Tue
// Wed
// Thu
// Fri
,
// Example of adding attributes to an enumeration by extending the Enumeration.Val class
object Planet extends Enumeration {
  protected case class Val(mass: Double, radius: Double) extends super.Val {
    def surfaceGravity: Double = Planet.G * mass / (radius * radius)
    def surfaceWeight(otherMass: Double): Double = otherMass * surfaceGravity
  }
  import scala.language.implicitConversions
  implicit def valueToPlanetVal(x: Value): Val = x.asInstanceOf[Val]

  val G: Double = 6.67300E-11
  val Mercury = Val(3.303e+23, 2.4397e6)
  val Venus   = Val(4.869e+24, 6.0518e6)
  val Earth   = Val(5.976e+24, 6.37814e6)
  val Mars    = Val(6.421e+23, 3.3972e6)
  val Jupiter = Val(1.9e+27, 7.1492e7)
  val Saturn  = Val(5.688e+26, 6.0268e7)
  val Uranus  = Val(8.686e+25, 2.5559e7)
  val Neptune = Val(1.024e+26, 2.4746e7)
}

println(Planet.values.filter(_.radius > 7.0e6))
// output:
// Planet.ValueSet(Jupiter, Saturn, Uranus, Neptune)

Yet, another popular approach is to model an enumeration using a combination of sealed trait and case object, and that is the purpose of this tutorial. In addition. well point that the exciting fact that Scala 3 will be providing the enum keyword!

 

Steps

1. How to create an Enumeration
As a reminder, and as per the Scala documentation, the Enumeration class is designed to be fairly lightweight as opposed to using case classes. To define an enumeration for a given domain, you basically need to extend the Enumeration class. In the example below, we are making use of a Singleton Object named Donut, which extends Enumeration, and thereafter provides various distinct values that make up different types of Donut.


println("Step 1: How to create an enumeration")
object Donut extends Enumeration {
  type Donut = Value

  val Glazed      = Value("Glazed")
  val Strawberry  = Value("Strawberry")
  val Plain       = Value("Plain")
  val Vanilla     = Value("Vanilla")
}

You can access each corresponding value explicitly, such as, Donut.Vanilla. You can further add an import statement - that is, import Donut._ - and then access each of the values directly. In addition, you benefit from a built-in id property, and it identifies a given value from the underlying enumerated values.

 


println(s"Vanilla Donut string value = ${Donut.Vanilla}")
println(s"Vanilla Donut's id = ${Donut.Vanilla.id}")

You should see the following output when you run your Scala application in IntelliJ:


Step 1: How to create and use an enumeration 
Vanilla Donut string value = Vanilla
Vanilla Donut’s id = 3

It is worth noting that the Vanilla id is at location 3 because the initial index for the enumerated values is zero based. That is, val Glazed is at index 0, val Strawberry is at index 1, and val Plain is at index 2, respectively.

 

2. Defining Algebraic Data Types using sealed traits

In most real-world production Scala code, the above Enumeration class has faced a fair bit of criticism over the years. Amongst others, perhaps a compelling one is that you do not benefit from the relevant type safety over exhaustive checking against valid enumerated values. After all, Scala is all about type safety as a first class citizen in the programming language.

 

As an alternative, and especially if you are modeling a particular Algebraic Data Type, short for ADT, you can use a sealed trait paired with case object. This approach will take full advantage of the compile time exhaustive checking, and can therefore be rather practical in, say, Pattern Matching. For illustration purposes, we show an ADT from our Akka tutorial. The ADT is designed for message passing with an Akka Actor using, say, Akka’s Tell Pattern. Consider, for instance, sending a BakeDonut event to an Actor named donutActor - that is, donutActor ! BakeDonut. That being said, we define a base sealed trait named BakingEvents, and represent each enumerated value using case object that extends the base trait.


println("\nStep2: Defining Algebraic Data Types using sealed traits")
sealed trait BakingEvents 
final case object BakeDonut extends BakingEvents 
final case object AddTopping extends BakingEvents 
final case object StopBaking extends BakingEvents 

Next, we create a method that will Patter Match on BakingEvents, and will output the corresponding event.

def prettyPrintBakingEvent(bakingEvent: BakingEvents): Unit = bakingEvent match{ 
case BakeDonut => println("BakeDonut event was used in Pattern Matching")
case AddTopping => println("AddTopping event was used in Pattern Matching") 
case StopBaking => println("StopBaking event was used in Pattern Matching") 
} 

And, finally, you can call the above method, such as, prettyPrintBakingEvent(BakeDonut).
You should see the following output when you run your Scala application in IntelliJ:


Step 2: Defining Algebraic Data Types using sealed traits 
BakeDonut event was used in Pattern Matching

 

3. Upcoming Scala 3 enum keyword

As we've previously mentioned, Scala 3 will bring forward a new enum keyword to facilitate enum, or enumeration, or ADT creation. For instance, in Scala 3, we should be able to model the above BakingEvents ADT as follows:


enum BakingEvents {
  case BakeDonut, AddTopping, StopBaking
}

And that is without any doubt a lot simpler and much nicer to use, while retaining the full functionality of an enumeration. Besides, you will be able to, for instance, change the default zero based index of the subsequent enumerated types as follows:


enum BakingEvents(val index: Int) {
  case BakeDonut extends BakingEvents(1)
  case AddTopping extends BakingEvents(2)
  case StopBaking extends BakingEvents(3)
}

This concludes our tutorial on Enums and Algebraic Data Types (ADT) and I hope you've found it useful!

 

Stay in touch via Facebook and Twitter for upcoming tutorials!

 

Don't forget to like and share this page :)

Summary

In this tutorial, we went over the following:

  • How to create an Enumeration
  • Defining Algebraic Data Types using sealed traits
  • Upcoming Scala 3 enum keyword

Tip

  • As a reminder, and with great enthusiasm, we all await the upcoming enum keyword in the forthcoming Scala 3. That is obviously thanks to the numerous accomplishments from the amazing work on Dotty.

Source Code

The source code is available on the allaboutscala GitHub repository.

What's Next

I hope that by going through the tutorials in Chapter 2, you should now have a basic understanding of the Scala programming syntax and features.

 

Perhaps this would be a good place to move on to the Chapter 3 on Functions.

 

Stay tuned!

Nadim Bahadoor on FacebookNadim Bahadoor on GithubNadim Bahadoor on LinkedinNadim Bahadoor on Twitter
Nadim Bahadoor
Technology and Finance Consultant with over 14 years of hands-on experience building large scale systems in the Financial (Electronic Trading Platforms), Risk, Insurance and Life Science sectors. I am self-driven and passionate about Finance, Distributed Systems, Functional Programming, Big Data, Semantic Data (Graph) and Machine Learning.
Other allaboutscala.com tutorials you may like: