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 Shape25.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)