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 = {}
    };
Let us create a trait called Trip which accept traveler T. The trip which we are planning is a mountain trip but for Bikers.
    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);
If you create another class SportsHarleyTrip which accepts SportsHarleyBiker which is a sub class of HarleyBiker and then pass to the method tripRegistration will give the error as it is against contravariance.
    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

comments powered by Disqus