Scala: How to define a collection class whose elements are all of some base type (inheritance)

This is an excerpt from the 1st Edition of the Scala Cookbook (partially modified for the internet). This is Recipe 19.6, “How to define a Scala collection type whose elements are all of some base type.”

Problem

You want to specify that a Scala class (or method) takes a generic type parameter, and that parameter is limited so it can only be that base type or a sub-type of that base type.

Solution

Define the Scala class or method by specifying the type parameter with an upper bound. To demonstrate this, create a simple type hierarchy:

trait CrewMember
class Officer extends CrewMember
class RedShirt extends CrewMember
trait Captain
trait FirstOfficer
trait ShipsDoctor
trait StarfleetTrained

Then create a few instances:

val kirk = new Officer with Captain
val spock = new Officer with FirstOfficer
val bones = new Officer with ShipsDoctor

Given this setup, imagine that you want to create a collection of officers on a ship, like this:

val officers = new Crew[Officer]()
officers += kirk
officers += spock
officers += bones

The first line lets you create officers as a collection that can only contain types that are an Officer, or subtype of an Officer.

In this example, those who are of type RedShirt won’t be allowed in the collection, because they don’t extend Officer:

val redShirt = new RedShirt
officers += redShirt  // ERROR: this won't compile

To enable this functionality and let Crew control which types are added to it, define it with an upper bound while extending ArrayBuffer:

class Crew[A <: CrewMember] extends ArrayBuffer[A]

This states that any instance of Crew can only ever have elements that are of type CrewMember. In this example, this lets you define officers as a collection of Officer, like this:

val officers = new Crew[Officer]()

It also prevents you from writing code like this, because String does not extend CrewMember:

// error: won't compile
val officers = new Crew[String]()

In addition to creating a collection of officers, you can create a collection of RedShirts, if desired:

val redshirts = new Crew[RedShirt]()

(I don’t know the names of any redshirts, otherwise I’d add a few to this collection.)

Typically you’ll define a class like Crew so you can create specific instances as shown.

You’ll also typically add methods to a class like Crew that are specific to the type (CrewMember, in this case). By controlling what types are added to Crew, you can be assured that your methods will work as desired. For instance, Crew could have methods like beamUp, beamDown, goWhereNoOneElseHasGone, etc. — any method that makes sense for a CrewMember.

Discussion

This type is referred to as a bound, specifically an upper bound.

If you’re working with an implicit conversion, you’ll want to use a view bound instead of an upper bound. To do this, use the <% symbol instead of the <: symbol.

You can use the same technique when you need to limit your class to take a type that extends multiple traits. For example, to create a Crew that only allows types that extend CrewMember and StarfleetTrained, declare the Crew like this:

class Crew[A <: CrewMember with StarfleetTrained] extends ArrayBuffer[A]

If you adapt the officers to work with this new trait:

val kirk = new Officer with Captain with StarfleetTrained
val spock = new Officer with FirstOfficer with StarfleetTrained
val bones = new Officer with ShipsDoctor with StarfleetTrained

you can still construct a list of officers, with a slight change to the Crew definition:

val officers = new Crew[Officer with StarfleetTrained]()

officers += kirk
officers += spock
officers += bones

This approach works as long as the instances have those types somewhere in their lineage (class hierarchy). For instance, you can define a new StarfleetOfficer like this:

class StarfleetOfficer extends Officer with StarfleetTrained

You could then define the kirk instance like this:

val kirk = new StarfleetOfficer with Captain

With this definition, kirk can still be added to the officers collection; the instance still extends Officer and StarfleetTrained.

Methods

Methods can also take advantage of this syntax. For instance, you can add a little behavior to CrewMember and RedShirt:

trait CrewMember {
    def beamDown { println("beaming down") }
}

class RedShirt extends CrewMember {
    def putOnRedShirt { println("putting on my red shirt") }
}

With this behavior, you can write methods to work specifically on their types. This method works for any CrewMember:

def beamDown[A <: CrewMember](crewMember: Crew[A]) {
    crewMember.foreach(_.beamDown)
}

But this method will only work for RedShirt types:

def getReadyForDay[A <: RedShirt](redShirt: Crew[A]) {
    redShirt.foreach(_.putOnRedShirt)
}

In both cases, you control which type can be passed into the method using an appropriate upper bound definition on the method’s type parameter.

See Also

  • Recipe 19.3, “Using Duck Typing (Structural Types)”
  • Scala also includes a lower type bound, though it is used less frequently. A lower bound is briefly demonstrated in Recipe 19.8, “Building Functionality with Types”. The page titled “A Tour of Scala: Lower Type Bounds” also describes a situation where a lower type bound might be used.