The -
symbol marks a type parameter as contravariant - here we say that "Handler
is contravariant on A
":
trait Handler[-A] {
def handle(a: A): Unit
}
A contravariant type parameter can be thought of as an "input" type. Marking A
as contravariant asserts that Handler[X] <: Handler[Y]
provided that X >: Y
. For example a Handler[Animal]
is a valid Handler[Cat]
, as a Handler[Animal]
must also handle cats.
A contravariant type parameter cannot appear in covariant (output) position. The following example will not compile as we are asserting that a Contra[Animal] <: Contra[Cat]
, however a Contra[Animal]
has def produce: Animal
which is not guaranteed to produce cats as required by Contra[Cat]
!
trait Contra[-A] {
def handle(a: A): Unit
def produce: A
}
Beware however: for the purposes of overloading resolution, contravariance also counterintuitively inverts the specificity of a type on the contravariant type parameter - Handler[Animal]
is considered to be "more specific" than Handler[Cat]
.
As it is not possible to overload methods on type parameters, this behavior generally only becomes problematic when resolving implicit arguments. In the following example ofCat
will never be used, as the return type of ofAnimal
is more specific:
implicit def ofAnimal: Handler[Animal] = ???
implicit def ofCat: Handler[Cat] = ???
implicitly[Handler[Cat]].handle(new Cat)
This behavior is currently slated to change in dotty, and is why (as an example) scala.math.Ordering
is invariant on its type parameter T
. One workaround is to make your typeclass invariant, and type-parametrize the implicit definition in the event that you want it to apply to subclasses of a given type:
trait Person
object Person {
implicit def ordering[A <: Person]: Ordering[A] = ???
}