Claude
Skills
Sign in
Back

Scala Type System

Included with Lifetime
$97 forever

Use when scala's advanced type system including generics, variance, type bounds, implicit conversions, type classes, higher-kinded types, path-dependent types, and abstract type members for building type-safe, flexible APIs.

General

What this skill does


# Scala Type System

## Introduction

Scala features one of the most sophisticated type systems among mainstream
programming languages, combining object-oriented and functional programming
concepts. This advanced type system enables precise modeling of domain concepts,
compile-time verification of complex constraints, and highly reusable abstractions.

Key features include parametric polymorphism (generics), variance annotations,
type bounds, implicit conversions and parameters, type classes, higher-kinded
types, path-dependent types, and abstract type members. These features enable
expressive APIs while maintaining type safety.

This skill covers generics and variance, upper and lower type bounds, view and
context bounds, implicit conversions, type classes, higher-kinded types,
path-dependent types, and practical type-level programming patterns.

## Generics and Type Parameters

Type parameters enable writing reusable code that works with multiple types while
maintaining type safety.

```scala
// Basic generic class
class Box[T](val content: T) {
  def get: T = content
  def map[U](f: T => U): Box[U] = new Box(f(content))
}

val intBox = new Box(42)
val stringBox = new Box("hello")

// Generic methods
def identity[T](x: T): T = x

def swap[A, B](pair: (A, B)): (B, A) = (pair._2, pair._1)

// Multiple type parameters
class Pair[A, B](val first: A, val second: B) {
  def swap: Pair[B, A] = new Pair(second, first)
}

// Generic collections
def first[T](list: List[T]): Option[T] = list.headOption

def last[T](list: List[T]): Option[T] = list.lastOption

// Type parameter constraints with type bounds
class NumberBox[T <: Number](val value: T) {
  def doubleValue: Double = value.doubleValue()
}

// Generic trait
trait Container[T] {
  def add(item: T): Container[T]
  def get: T
  def isEmpty: Boolean
}

class SimpleContainer[T](private var item: Option[T] = None)
  extends Container[T] {
  def add(newItem: T): Container[T] = {
    item = Some(newItem)
    this
  }

  def get: T = item.getOrElse(throw new NoSuchElementException)

  def isEmpty: Boolean = item.isEmpty
}

// Generic companion object
object Container {
  def empty[T]: Container[T] = new SimpleContainer[T]()

  def of[T](item: T): Container[T] = new SimpleContainer[T](Some(item))
}

// Type parameter inference
val box1 = new Box(42)           // Box[Int]
val box2 = new Box("hello")      // Box[String]
val list1 = List(1, 2, 3)        // List[Int]

// Explicit type parameters when needed
val box3 = new Box[Any](42)
val emptyList = List.empty[String]

// Generic functions with multiple constraints
def max[T](a: T, b: T)(implicit ord: Ordering[T]): T =
  if (ord.gt(a, b)) a else b

println(max(5, 10))              // 10
println(max("apple", "banana"))  // banana

// Type aliases for complex generic types
type StringMap[V] = Map[String, V]
type IntPair = (Int, Int)

val userAges: StringMap[Int] = Map("Alice" -> 30, "Bob" -> 25)
val point: IntPair = (10, 20)
```

Generics enable writing code once and reusing it with multiple types while
maintaining compile-time type safety.

## Variance Annotations

Variance controls how parameterized types relate to each other based on their
type parameters' subtyping relationships.

```scala
// Covariance (+T): if A <: B, then Container[A] <: Container[B]
class CovariantBox[+T](val content: T) {
  def get: T = content
  // Can't have T in contravariant position (method parameters)
  // def set(item: T): Unit = ???  // Won't compile
}

class Animal
class Dog extends Animal
class Cat extends Animal

val dogBox: CovariantBox[Dog] = new CovariantBox(new Dog)
val animalBox: CovariantBox[Animal] = dogBox  // Valid due to covariance

// Contravariance (-T): if A <: B, then Container[B] <: Container[A]
trait Printer[-T] {
  def print(item: T): Unit
}

class AnimalPrinter extends Printer[Animal] {
  def print(animal: Animal): Unit = println("Animal")
}

val animalPrinter: Printer[Animal] = new AnimalPrinter
val dogPrinter: Printer[Dog] = animalPrinter  // Valid due to contravariance

// Invariance (T): no subtyping relationship
class InvariantBox[T](private var content: T) {
  def get: T = content
  def set(item: T): Unit = { content = item }
}

// Practical variance example: Function1
trait Function1[-T, +R] {
  def apply(v: T): R
}

val animalToString: Function1[Animal, String] = animal => "Animal"
val dogToAny: Function1[Dog, Any] = animalToString  // Valid

// Collections variance
val dogs: List[Dog] = List(new Dog, new Dog)
val animals: List[Animal] = dogs  // List is covariant

// Mutable collections are invariant
val dogArray: Array[Dog] = Array(new Dog)
// val animalArray: Array[Animal] = dogArray  // Won't compile

// Option is covariant
val someDog: Option[Dog] = Some(new Dog)
val someAnimal: Option[Animal] = someDog  // Valid

// Variance with multiple type parameters
class Function2[-T1, -T2, +R] {
  def apply(v1: T1, v2: T2): R = ???
}

// Variance bounds in definition
class Box[+T](val content: T) {
  // Use lower bound to allow contravariant position
  def set[U >: T](item: U): Box[U] = new Box(item)
}

// Either is covariant in both parameters
sealed trait Either[+A, +B]
case class Left[A](value: A) extends Either[A, Nothing]
case class Right[B](value: B) extends Either[Nothing, B]

val rightInt: Either[String, Int] = Right(42)
val rightAny: Either[String, Any] = rightInt  // Valid
```

Variance annotations make parameterized types more flexible while maintaining
type safety, especially for immutable containers.

## Type Bounds

Type bounds constrain type parameters to specific type hierarchies, enabling
type-safe operations on generic types.

```scala
// Upper type bound (T <: Upper)
def findMax[T <: Ordered[T]](list: List[T]): Option[T] = {
  if (list.isEmpty) None
  else Some(list.reduce((a, b) => if (a > b) a else b))
}

// Lower type bound (T >: Lower)
class Animal
class Dog extends Animal

class Container[+T] {
  def add[U >: T](item: U): Container[U] = ???
}

// Using both bounds together
def cloneAndReset[T >: Null <: Cloneable](obj: T): T = {
  val cloned = obj.clone().asInstanceOf[T]
  cloned
}

// Complex type bounds
trait Comparable[T] {
  def compareTo(that: T): Int
}

def sort[T <: Comparable[T]](list: List[T]): List[T] =
  list.sortWith(_.compareTo(_) < 0)

// Multiple bounds with 'with'
trait Loggable {
  def log(): Unit
}

trait Serializable {
  def serialize(): String
}

def process[T <: Loggable with Serializable](item: T): String = {
  item.log()
  item.serialize()
}

// Recursive type bounds (F-bounded polymorphism)
trait Comparable2[T <: Comparable2[T]] { self: T =>
  def compare(that: T): Int
}

class Person(val name: String, val age: Int) extends Comparable2[Person] {
  def compare(that: Person): Int = this.age - that.age
}

// View bounds (deprecated but useful to understand)
// def sum[T <% Ordered[T]](list: List[T]): T = ???

// Context bounds (modern approach)
def sum[T: Numeric](list: List[T]): T = {
  val numeric = implicitly[Numeric[T]]
  list.foldLeft(numeric.zero)(numeric.plus)
}

println(sum(List(1, 2, 3)))           // 6
println(sum(List(1.5, 2.5, 3.0)))     // 7.0

// Multiple context bounds
def print[T: Ordering: Numeric](list: List[T]): Unit = {
  val ord = implicitly[Ordering[T]]
  val num = implicitly[Numeric[T]]
  println(s"Max: ${list.max(ord)}, Sum: ${sum(list)}")
}

// Abstract type members with bounds
trait Container2 {
  type Content <: AnyRef
  def get: Content
}

class StringContainer extends Container2 {
  type Content = String
  def get: String = "hello"
}

// Type bounds with variance
class Box[+T] {
  def put[U >: T](item: U): Box[U] = new Box[U]
}

// Existential types (less common)
def processAnyBox(box: Box[_]): Unit = {
  println("Processing box")
}

// Type bounds for type classes
trait Show[T] {
  def show(value: T): String
}

def display[T: Show](value: T): String = {
  val shower = implicitly[Show[T]]
  shower.show(value)
}

implicit val intShow: Show[Int] = (value: Int) => value.toString
impli

Related in General