This is an excerpt from the Scala Cookbook (partially modified for the internet). This is the introduction to Chapter 19, Scala Types.
As you can tell from one look at the Scaladoc for the collections classes, Scala has a powerful type system. However, unless you’re the creator of a library, you can go a long way in Scala without having to go too far down into the depths of Scala types. But once you start creating collections-style APIs for other users, you will need to learn them.
This chapter provides recipes for the most common problems you’ll encounter, but when you need to go deeper, I highly recommend the book, Programming in Scala, by Odersky, Spoon, and Venners. (Martin Odersky is the creator of the Scala programming language, and I think of that book as “the reference” for Scala.)
Scala’s type system uses a collection of symbols to express different generic type concepts, including variance, bounds, and constraints. The most common of these symbols are summarized in the next sections.
Variance in Scala
Type variance is a “generic type” concept, and defines the rules by which parameterized types can be passed into methods. The type variance symbols are briefly summarized in Table 19-1.
Table 19-1. Descriptions of type variance symbols
Symbols | Name | Description |
---|---|---|
Array[T] |
Invariant | Used when elements in the container are mutable. Example: Can only pass Array[String] to a method expecting Array[String] . |
Seq[+A] |
Covariant | Used when elements in the container are immutable. This makes the container more flexible. Example: Can pass a Seq[String] to a method expected Seq[Any] . |
Foo[-A] Function1[-A, +B] |
Contravariant | Contravariance is essentially the opposite of covariance, and is rarely used. See Scala’s Function1 trait for an example of how it is used. |
The following examples, showing what code will and won’t compile with the Grandparent, Parent, and Child classes, can also be a helpful reference to understanding variance:
class Grandparent class Parent extends Grandparent class Child extends Parent class InvariantClass[A] class CovariantClass[+A] class ContravariantClass[-A] class VarianceExamples { def invarMethod(x: InvariantClass[Parent]) {} def covarMethod(x: CovariantClass[Parent]) {} def contraMethod(x: ContravariantClass[Parent]) {} invarMethod(new InvariantClass[Child]) // ERROR - won't compile invarMethod(new InvariantClass[Parent]) // success invarMethod(new InvariantClass[Grandparent]) // ERROR - won't compile covarMethod(new CovariantClass[Child]) // success covarMethod(new CovariantClass[Parent]) // success covarMethod(new CovariantClass[Grandparent]) // ERROR - won't compile contraMethod(new ContravariantClass[Child]) // ERROR - won't compile contraMethod(new ContravariantClass[Parent]) // success contraMethod(new ContravariantClass[Grandparent]) // success }
Per Wikipedia, in programming, variance “refers to how subtyping between more complex types (list of
Cats
versus list ofAnimals
, function returningCat
versus function returningAnimal
, ...) relates to sub-typing between their components.”
Scala “bounds”
Bounds let you place restrictions on type parameters. Table 19-2 shows the common bounds symbols.
Table 19-2. Descriptions of Scala’s bounds symbols
Name | Description | |
---|---|---|
A <: B |
Upper bound | A must be a subtype of B . See Recipe 19.6. |
A >: B |
Lower bound | A must be a supertype of B . Not commonly used. See Recipe 19.8. |
A <: Upper >: Lower |
Lower and upper bounds used together | The type A has both an upper and lower bound. |
The first version of the O’Reilly book, Programming Scala, has a nice tip that helps me remember these symbols. The authors state that in UML diagrams, subtypes are shown below supertypes, so when I see
A <: B
, I think, “A
is less thanB
...A
is underB
...A
is a subtype ofB
.”
Lower bounds are demonstrated in several methods of the collections classes. To find some lower bound examples, search the Scaladoc of classes like List
for the >:
symbol.
There are several additional symbols for bounds. For instance, a view bound is written as A <% B
, and a context bound is written as T : M
. These symbols are not covered in this book; see the book, Programming in Scala (Odersky, et al.), for details and examples of their use.
Scala Type Constraints
Scala lets you specify additional type constraints. These are written with these symbols:
A =:= B // A must be equal to B A <:< B // A must be a subtype of B A <%< B // A must be viewable as B
These symbols are not covered in this book. See the book, Programming in Scala, for details and examples. Twitter’s Scala School Advanced Types page also shows brief examples of their use, where they are referred to as “type relation operators.”
this post is sponsored by my books: | |||
![]() #1 New Release |
![]() FP Best Seller |
![]() Learn Scala 3 |
![]() Learn FP Fast |
Type Examples in Other Chapters
Because types are naturally used in many solutions, you can find some recipes related to types in other chapters:
- Recipe 2.2, “Converting Between Numeric Types (Casting)” and Recipe 2.3 demonstrate ways to convert between types.
- Recipe 5.9, “Supporting a Fluent Style of Programming” demonstrates how to return
this.type
from a method. - Implicit conversions let you add new behavior to closed types like
String
, which is declaredfinal
in Java. They are demonstrated in Recipe 1.10, “Add Your Own Methods to the String Class” and Recipe 2.1, “Parsing a Number from a String”. - Recipe 6.1, “Object Casting” demonstrates how to cast objects from one type to another.
Finally, Recipe 19.8, “Building Functionality with Types” combines several of the concepts described in this chapter, and also helps to demonstrate Scala’s call-by-name feature.