Variance describes how subtyping between type arguments affects subtyping between parameterized types. With covariance, declaring List[+A] makes List[Dog] a subtype of List[Animal] whenever Dog is a subtype of Animal. section 3.1
Why covariance helps
Nil contains no elements, so Scala can treat it as List[Int], List[Double], or another List type. This follows from Nil extending List[Nothing], Nothing being a subtype of every type, and List being covariant. Omitting the + would make List invariant and remove that subtype relationship. section 3.1
Covariance also lets a Free[Console, A] become a Free[[x] =>> Console[x] | Files[x], A], because Console is accepted as part of the larger effect algebra. However, flatMap exposes F as an input to a function, placing it in a contravariant position. Scala therefore requires a widened F2 supertype and returns Free[F2, B]. section 13.5.1
When working with a covariant parameter:
- Use the subtype relationship to replace manual union conversions when a smaller effect algebra must become a larger one. section 13.5.1
- Treat compiler variance errors as signals to widen the affected type parameter, as flatMap does with F2. section 13.5.1
- Remember that annotations are optional in some definitions, though omitting them can worsen type inference. section 3.1
enum Free[+F[_], A]: #1
case Return(a: A) extends Free[Nothing, A] #2
case Suspend(s: F[A])
case FlatMap[F[_], A, B](
s: Free[F, A],
f: A => Free[F, B]) extends Free[F, B]
def flatMap[F2[x] >: F[x], B](
f: A => Free[F2, B]): Free[F2, B] = #3
FlatMap(this, f)
Inspect the covariant Free declaration, its Nothing case, and the widened flatMap signature together. section 13.5.1
