scalaadvanced

Generics Variance and Type Bounds

Use Scala generics with covariance, contravariance, upper/lower bounds, and context bounds.

scala
// Covariant container (+T): if A <: B, then Box[A] <: Box[B]
sealed trait Box[+A]:
  def map[B](f: A => B): Box[B]
  def flatMap[B](f: A => Box[B]): Box[B]

case class Full[+A](value: A) extends Box[A]:
  def map[B](f: A => B): Box[B] = Full(f(value))
  def flatMap[B](f: A => Box[B]): Box[B] = f(value)

case object Empty extends Box[Nothing]:
  def map[B](f: Nothing => B): Box[B] = Empty
  def flatMap[B](f: Nothing => Box[B]): Box[B] = Empty

// Contravariant (-T): Printer[Animal] <: Printer[Dog]
trait Printer[-A]:
  def print(value: A): String

class AnimalPrinter extends Printer[Animal]:
  def print(value: Animal): String = s"Animal: ${value.name}"

trait Animal:
  def name: String

case class Dog(name: String, breed: String) extends Animal
case class Cat(name: String) extends Animal

// Upper bound: T must be subtype of Comparable
def maxOf[T <: Comparable[T]](a: T, b: T): T =
  if a.compareTo(b) >= 0 then a else b

// Lower bound: T must be supertype of A
def prepend[A, B >: A](list: List[A], elem: B): List[B] =
  elem :: list

// Context bound: requires given instance
def showAll[T: Show](items: List[T]): String =
  items.map(summon[Show[T]].show).mkString(", ")

trait Show[T]:
  def show(value: T): String

given Show[Int] with
  def show(value: Int): String = value.toString

given Show[String] with
  def show(value: String): String = s"\"$value\""

// Type class derivation
given [A: Show, B: Show]: Show[(A, B)] with
  def show(value: (A, B)): String =
    s"(${summon[Show[A]].show(value._1)}, ${summon[Show[B]].show(value._2)})"

@main def run(): Unit =
  // Covariance
  val dogBox: Box[Dog] = Full(Dog("Rex", "Lab"))
  val animalBox: Box[Animal] = dogBox  // OK: Box is covariant
  println(animalBox.map(_.name))

  val empty: Box[Dog] = Empty
  println(empty.map(_.name))  // Empty

  // Contravariance
  val animalPrinter: Printer[Animal] = AnimalPrinter()
  val dogPrinter: Printer[Dog] = animalPrinter  // OK: Printer is contravariant
  println(dogPrinter.print(Dog("Rex", "Lab")))

  // Context bounds
  println(showAll(List(1, 2, 3)))
  println(showAll(List("a", "b")))
  println(showAll(List((1, "a"), (2, "b"))))

Use Cases

  • Type-safe container libraries
  • Flexible API design with variance
  • Type class patterns with context bounds

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.