Enumerations in Scala 3
by Noel Markham
- •
- April 22, 2021
- •
- scala• scala3• functional programming• functional
- |
- 6 minutes to read.

This is the first post looking at a few of the new and updated features coming to Scala 3. Today, we are going to be looking at enumerations and how they have improved from the approaches taken in Scala 2.
Scala 2 had enumerations:
The intention with Scala 2 was to extends the Enumeration
class, giving your enumeration name and values in the enclosing definition:
object Directions extends Enumeration {
type Direction = Value
val North, South, East, West = Value
def f(d: Direction): Boolean = d == North
}
There were a few issues with this. In this example, North
, South
, East
, and West
aren’t full
first-class citizens: when used in pattern matching, you are not warned for non-exhaustive checks,
and they lose their underlying type after compilation, making method overloading pointless, if not impossible.
These enumerated values are val
fields, so you cannot do any more than you can do with a Java Enum
:
public enum Die {
One(1),
Two(2),
Three(3),
Four(4),
Five(5),
Six(6);
private int faceValue;
Die(int faceValue) {
this.faceValue = faceValue;
}
public int getFaceValue() {
return faceValue;
}
}
The above simply is not possible using the packaged Scala 2 enumeration approach.
There are a couple of ways around this:
Sealed hierarchies
You can create a sealed trait
(or sealed abstract class
) and provide all your possible instances:
sealed trait Direction
case object North extends Direction
case object South extends Direction
case object East extends Direction
case object West extends Direction
These directions are “plain old” Scala objects - there is nothing surprising here.
Similarly, you can add instance values to your Direction
type, so implementing the Die
type is straightforward.
Enumeratum library
The Enumeratum library is another approach to providing better enumeration support in Scala 2. It bridges the gap between the roll-your-own sealed trait
approach, providing many of the Enumeration
class features at compile-time, such as the index value of the type, or utility methods for moving to and from the instances based on their instance name:
import enumeratum._
sealed trait Direction
object Direction extends Enum[Direction] {
val values = findValues
case object North extends Direction
case object South extends Direction
case object East extends Direction
case object West extends Direction
}
Direction.withName("East")
// res0: Direction = East
It does suffer from some of the boilerplate seen with the regular Scala Enumeration
too.
Scala 3: Introducing the new enum
keyword
Scala 3 has introduced a new enum
keyword. Our direction enumeration can now be constructed like this:
enum Direction:
case North extends Direction
case South extends Direction
case East extends Direction
case West extends Direction
We have removed most of the boilerplate and unintuitive syntax: we are not extending a class, we don’t need to assign a type to the value, and we don’t need to assign our values.
Similarly, we can add instance data to the enumerated instances:
enum Die(val faceValue: Int):
case One extends Die(1)
case Two extends Die(2)
case Three extends Die(3)
case Four extends Die(4)
case Five extends Die(5)
case Six extends Die(6)
As with the original Enumeration
implementation, these new values come with many helpers, such as extracting all of the values,
or a particular index:
scala> val n = Direction.North
val n: Direction = North
scala> val six = Die.valueOf("Six")
val six: Die = Six
scala> val first = Die.fromOrdinal(0)
val first: Die = One
It is fine to work with the enum
similar to how you would with a class
, object
, or trait
: adding members in the form of
val
and def
is allowed.
Algebraic Data Types, too
A nice addition is that we can use the enum
to create ADT sum types, much like we would have done with the sealed trait
hierarchy:
enum BinaryTree[+A]:
case Node(value: A, left: BinaryTree[A], right: BinaryTree[A])
case Leaf
Note here that we do not need to explicitly state that Node
and Leaf
extend BinaryTree
.
In our original Direction
enumeration, we could have omitted the extends Direction
part.
scala> val tree = BinaryTree.Node("Hello", BinaryTree.Leaf, BinaryTree.Leaf)
val tree: BinaryTree[String] = Node(Hello,Leaf,Leaf)
scala> val leaf = BinaryTree.Leaf
val leaf: BinaryTree[Nothing] = Leaf
Similarly, Leaf
extends BinaryTree[Nothing]
: it does not need to be explicitly stated in the enum
definition.
The new enum
keyword is a welcome addition to the language, and should make for clearer code without the boilerplate
gymnastics often seen in Scala 2.
Keep a look out on our blog and Twitter account for more blog posts soon about new and improved features in Scala 3. We will have a few more posts in the coming weeks as Scala 3 is officially launched.