Scala covariant and contravariant
Published On: 2019/09/04
How to implement generics in scala is the question which leads us to the concept of variance. There are four types of variance, but we will look 3 of them in this article. The are many examples of the variance with Animal and Cat classes. As a travler, i would like to explain it with the example of different types of travelers.
Invariance [T]
A trait or class is said to be invariant if its parameter appeared as both input and output .In other words, you have to pass excatly the same parameter tpye. Niether the super type nor the subtype can be accepted as the parameter.
trait Traveler{
def travel(): Unit
}
case class Biker(name:String) extends Traveler{
def travel():Unit = {}
};
case class Backpacker(name:String) extends Traveler{
def travel():Unit = {}
};
trait Trip{
def start(): Unit
def stop(): Unit
}
class MountainTrip[T] extends Trip{
def start(): Unit = {}
def stop(): Unit = {}
def add(traveler: T): Unit = {} // Input is of T
def remove(traveler: T): Unit = {} // Input is of T
def travelers(): Iterable[T] = {return null} // output is of T
}
val bikerTrip = new MountainTrip[Biker]();
bikerTrip.add(Biker("Paul")); // adding is successfull
// adding a backerpacker is unsuccessfull because it is a bikers trip
bikerTrip.add(Backpacker("John"));
type mismatch; found : com.asyncstream.scala.variance.Invariance.Backpacker required: com.asyncstream.scala.variance.Invariance.Biker
Covariance [+T]
Covariance can be represented by the annotation +T in scala. List support covariance by allowing us to create a list of subtypes and that list can be assigned to the super type. In the below example allBikers list contains Bikers and HarleyBikers. This list can be converted to the TwoWheelerTraveler list as it is the super type of Biker and HarleyBiker.
val allBikers:List[Biker] = List(new Biker("Sam"),new HarleyBiker("John"))
val allTwoWheelerTravelers:List[TwoWheelerTraveler] = allBikers;
Contravariance [-T]
Covariance can be represented by the annotation +T in scala. This means that the type T or its super types can be passed as an argument which is opposite to the covariance.
abstract class MotorTrip[-T] extends Trip{
def start(): Unit = {}
def stop(): Unit = {}
def register(traveler:T): Unit // in parameter is the contravarient
}
class BikerTrip extends MotorTrip[Biker]{
def register(traveler:Biker): Unit ={}
}
class HarleyTrip extends MotorTrip[HarleyBiker]{
def register(traveler:HarleyBiker): Unit ={}
}
val motorBikerTrip: MotorTrip[Biker] = new BikerTrip
val harlyBikerTrip: MotorTrip[HarleyBiker] = new HarleyTrip
val harleyBiker = new HarleyBiker("David");
def tripRegistration(motorTrip:MotorTrip[HarleyBiker]):Unit ={
motorTrip.register(harleyBiker);
}
tripRegistration(motorBikerTrip);
tripRegistration(harlyBikerTrip);
class SportsHarleyTrip extends MotorTrip[SportsHarleyBiker]{
def register(traveler:SportsHarleyBiker): Unit ={}
}
val sportsHarlyBikerTrip: MotorTrip[SportsHarleyBiker] = new SportsHarleyTrip
//ERROR
tripRegistration(sportsHarlyBikerTrip);
Conclusion
In this post we looked into three types of variance representation in scala. For the sample code please visit the git repository Scala Variance