Generics, Variance & Bounds

Rules: covariant type parameters may appear only in output positions; contravariant only in input positions.

25.1 Generic classes and methods

class Box[A](val value: A):
  def map[B](f: A => B): Box[B] = Box(f(value))

def firstOrDefault[A](xs: List[A], default: A): A = xs.headOption.getOrElse(default)

case class Pair[A, B](first: A, second: B):
  def swap: Pair[B, A] = Pair(second, first)

25.2 Variance

Meaning (if Cat <: Animal) Box[Cat] <: Box[Animal] Box[Animal] <: Box[Cat]

Rules: covariant type parameters may appear only in output positions; contravariant only in input positions. Mutable containers (Array, mutable.Buffer) are invariant.

sealed trait Animal; class Cat extends Animal; class Dog extends Animal

val cats: List[Cat] = List(Cat())
val animals: List[Animal] = cats            // List is covariant: List[+A]

trait Printer[-A]:
  def print(a: A): Unit
val animalPrinter: Printer[Animal] = a => println(a)
val catPrinter: Printer[Cat] = animalPrinter  // contravariant

// Functions: Function1[-A, +B]

25.3 Lower bounds to make covariant containers usable

enum MyList[+A]:
  case Empty
  case Cons(head: A, tail: MyList[A])

  def prepend[B >: A](b: B): MyList[B] = Cons(b, this)

val xs: MyList[Cat] = MyList.Cons(Cat(), MyList.Empty)
val ys: MyList[Animal] = xs.prepend(Dog())   // widened to MyList[Animal]

25.4 Upper bounds

def maxOf[A <: Comparable[A]](a: A, b: A): A = if a.compareTo(b) >= 0 then a else b

trait Shape { def area: Double }
def largest[S <: Shape](shapes: List[S]): S = shapes.maxBy(_.area)   // returns S, not Shape

25.5 Context bounds (preview of type classes)

def sortAll[A: Ordering](xs: List[A]): List[A] = xs.sorted
// desugars to: def sortAll[A](xs: List[A])(using Ordering[A])

25.6 ClassTag & TypeTest — overcoming erasure

import scala.reflect.ClassTag
def makeArray[A: ClassTag](n: Int, a: A): Array[A] = Array.fill(n)(a)

def filterType[A: ClassTag](xs: List[Any]): List[A] = xs.collect { case a: A => a }
filterType[String](List(1, "a", 2.0, "b"))   // List(a, b)