The following article provides a list of all the different type parameterisations available in Scala and how to use them.
Variance
A class can depend on a type constructor. The following is an example trait that depends on a generic type T:
trait MyExample[T] {...}
The above declaration implies that the generic type T is the only valid parameter for MyExample: this is called nonvariance.
Let’s assume we would like our trait to be compatible with other types, as long as this types are subclasses of T. This can be expressed as following:
trait MyExample[+T] {...}
This concept is called covariance: Given a type U subtype of T, a class MyClass is called covariant if MyClass[U] is a subtype of MyClass[T].
What if we want to achieve the opposite? In other words, we would like our trait to be compatible with any type as long as they are superclass of type T:
trait MyExample[-T] {...}
This concept is called contravariance: Given a type S supertype of T, a class MyClass is called contravariant if MyClass[S] is a subtype of MyClass[T].
Bounds
Compatibility between classes is not the only thing we can achieve. For example, we would like our trait MyExample to accept all the types that are subclasses of T. This can be expressed using the lower bound annotation:
trait MyExample[U >: T] {...}
The upper bound annotation can be used in a similar way to let our MyExample trait accept all the types that are superclasses of T:
trait MyExample[S <: T] {...}
Last but not least, let’s assume we want our trait to accept any type as long as convertible to T. This can be achieved using the view bound annotation:
trait MyExample[V <%: T] {...}
Thank you M. Odersky for implicit conversions!