Reading — step 1 of 5
Learn
~1 min readType System Power
Variance decides how generic types relate when their parameters relate. List[Dog] should be a List[Animal] — but is it? Depends on variance.
Three modes:
class Container[T]— invariant:Container[Dog]is NOT aContainer[Animal]class Container[+T]— covariant:Container[Dog]IS aContainer[Animal](subtypes flow with subtypes)class Container[-T]— contravariant:Container[Animal]IS aContainer[Dog](subtypes flow against)
class Animal
class Dog extends Animal
class Box[T](val item: T) // invariant
class ReadOnlyBox[+T](val item: T) // covariant
class Sink[-T](val accept: T => Unit) // contravariant
Why variance matters:
val dogs: ReadOnlyBox[Dog] = new ReadOnlyBox(new Dog)
val animals: ReadOnlyBox[Animal] = dogs // works because + means covariant
val invariantDogs: Box[Dog] = new Box(new Dog)
// val invariantAnimals: Box[Animal] = invariantDogs // ERROR — invariant
The rule:
- If T appears only as output (return type, val): make it covariant (
+T) - If T appears only as input (parameter): make it contravariant (
-T) - If T appears as both: must stay invariant
This is PECS for Scala (similar to Java's wildcards).
Function1[-A, +R] — Scala's function type:
- Argument
Ais contravariant (aFunction1[Animal, ...]works whereFunction1[Dog, ...]is expected) - Result
Ris covariant
Practical use:
sealed trait Result[+T]
case class Ok[+T](value: T) extends Result[T]
case object Err extends Result[Nothing] // Nothing is bottom — subtype of everything
val r: Result[Int] = Err // works because Result is covariant + Nothing <: Int
This is how None, Nil work — they're Option[Nothing] / List[Nothing] and fit anywhere a Option[T] / List[T] is expected.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…