Skip to the content.

Note: Lists (i.e., functional stacks) and variance

In Scala, a list (or stack) data structure will in fact typically be defined as follows:

enum Stack[+A]:
  case Empty extends Stack[Nothing]
  case Cons(head: A, tail: Stack[A])

Notice two important changes from the definition we saw before:

Since Empty is now a plain object (called a singleton) and no longer a data constructor instantiated with an empty argument list, the Scala compiler no longer knows what its relationship with the Stack type constructor should be. For Cons, it infers a Cons[A] type which extends the Stack[A] type. But objects cannot have type parameters, and Scala 3 does not know what Stack[?] the Empty object should extend. So we specify explicitly that it extends Stack[Nothing].

Covariance means that there is a subtyping relationship between stack types whose type arguments have a subtyping relationship, in the same direction. In other words, if A is a subtype of B, then Stack[A] is a subtype of Stack[B].

Since Nothing is the bottom of the Scala subtyping lattice, we have the useful relationship that, for any T, then Empty is a subtype of Stack[T]. This means we can use the same empty stack object as the root of stacks of arbitrary types! For instance, 1 :: 2 :: Empty and "a" :: "b" :: Empty.

Update: In fact, since a recent version of Scala 3, one can now omit the extends clause and Nothing is inferred:

enum Stack[+A]:
  case Empty
  case Cons(head: A, tail: Stack[A])