An introduction to Scala Types (from the Scala Cookbook)

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 of Animals, function returning Cat versus function returning Animal, ...) 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 than B ... A is under B ... A is a subtype of B.”

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.”

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 declared final 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.