Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Variance

Scala's type system includes variance annotations that influence how subtyping between more complex types works, such as between generic classes of those types.

  • Covariance (+T): If A is a subtype of B, then Box[A] is a subtype of Box[B].
  • Contravariance (-T): If A is a subtype of B, then Box[B] is a subtype of Box[A].
  • Invariance: By default, generic types in Scala are invariant. If A is a subtype of B, there is no relationship between Box[A] and Box[B].

Example of covariance:

class Container[+A]

val animalContainer: Container[Animal] = new Container[Cat]  // Cat is a subtype of Animal

Bounds

Scala allows you to restrict the types that can be used as type parameters through bounds.

  • Upper Bounds (<:): Specifies that a type parameter must be a subtype of a particular type.

    def printName[T <: Animal](animal: T): Unit = {
      println(animal.name)
    }
    
  • Lower Bounds (>:): Specifies that a type parameter must be a supertype of a particular type.

  • View Bounds (deprecated in Scala 2.11 and removed in Scala 3): Were used to demand that there exists an implicit conversion from a type T to another type.

  • Context Bounds ([T: Ordering]): Useful for requiring an implicit value of a certain type, such as an Ordering[T] for sorting.

Type Constraints

Scala also supports type constraints that allow more control over the relationships between type parameters.

  • <% (View Bound): Deprecated.
  • <:< (Upper Type Bound): Ensures one type is a subtype of another.
  • =:!= (Not Equal): Ensures two types are not the same.
def foo[A, B](a: A, b: B)(implicit ev: A <:< B): B = b

Exercises