This is an excerpt from the Scala Cookbook (partially modified for the internet). This is Recipe 19.6, “How to define a collection whose element are all of some base type.”
Problem
You want to specify that a class or method takes a type parameter, and that parameter is limited so it can only be a base type, or a subtype of that base type.
Solution
Define the 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.
... this post is sponsored by my books ... | |
![]() #1 New Release! |
![]() FP Best Seller |
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.