--- layout: [post, presentation] title: "CoVariant, ContraVariant and InVariant... Variances in Scala" date: 2017-10-10 00:00:00 +0200 reveal: theme: ../../../css/theme/whiteish center: true history: true tags: - scala - types - covariant - contravariant - invariant comments: true --- ## Variance The variance in Scala aims to provide flexibility to the inheritance on parametric types. Two examples where it helps are: - Make `List[Dog]` a subtype of `List[Animal]` - Reuse a function `Animal=>Boolean` as a function `Dog=>Boolean` ### A concrete example Consider the following example: ``` // Our pet classes sealed class Animal class Dog extends Animal class Cat extends Animal ``` Let's say you work for a veterinary. You're writing an API. You want to **modularize the functions that retrieve pets' information from a DB**, like: - `getName` - `getBreed` - etc. Then, we could define a class `Func` that will encapsulate an _information retriever_ function, `f`. Here is our first attempt: ``` class Func[I,O] (val f: I => O) { def apply(i: I): O = f(i) } ``` Good! We can define our first instance of `Func`: ``` // Our first information retriever // Is my animal a dog? val isADog: Func[Animal, Boolean] = { new Func((i: Animal) => i.isInstanceOf[Dog]) } ``` We say that **_Fun_ is invariant in `I` (`Animal`) and invariant in `O` (`Boolean`)**, as there is not subtype association done by the compiler. ## Generalizing `Func`: Covariance ### The problem Let's say you handle many `Func` implementations: ``` val x: Func[Animal, Boolean] = ... val y: Func[Animal, String] = ... val z: Func[Animal, Int] = ... ``` It would be good to be able to treat all `x`, `y` and `z` polymorphically. For instance be able to do: ``` val l: List[Func[Animal, AnyVal]] = List(x, y, z) // won't work, invariance ``` Our initial declaration of `Func[I, O]` was invariant in both `I` and `O`. It **does not allow this supertype relation**. ### The solution The solution is **covariance**. The principle: making `Clz` covariant in `A` means that - `Cat <: Animal` implies - `Clz[Cat] <: Clz[Animal]` In other words: the inheritance of this **parametric** type follows the one from the **parameter** type. Back to our example, we simply re-define `Func`, but making it covariant in `O`: ``` // now covariant on O class Func[I, +O] (val f: I => O) { def apply(i: I): O = f(i) } ``` ``` // specific type val isADog: Func[Animal, Boolean] = ... // generic type val covIsDogForAnyVal: Func[Animal, AnyVal] = isADog // assigned to a more general type // works because // Boolean <: AnyVal, // and thanks to covariance // Func[X, Boolean] <: Func[X, AnyVal] ``` ## Specializing `Func`: Contravariance ### The problem Let's say we have our function `Func[Animal, Boolean]`. Given that `Dog <: Animal` (`Dog` is a subtype of `Animal`), it seems natural to be able to apply such function to a `Dog` too. ### The solution The solution is **contravariance**. The principle: making `Clz` contravariant in `A` means that: - `Cat <: Animal` implies - `Clz[Cat] >: Clz[Animal]` In other words the inheritance of this parametric type follows inversely the one from the parameter type. We simply redefine `Func` but making it contravariant in `I` this time: ``` // now contravariant in I class Func[-I, O] (val f: I => O) { def apply(i: I): O = f(i) } ``` ``` // generic type val isDog: Func[Animal, Boolean] = new Func((i: Animal) => i.isInstanceOf[Dog]) // specific type val contrvarIsDog: Func[Dog, Boolean] = isDog // assigned to a more specific type // works because // Dog <: Animal, and thanks // to contravariance // Func[Dog, X] >: Func[Animal, X] ``` ## Use in the Scala library I invite you to take a look at the implementation of [the trait Function2 in Scala v2.12](https://github.com/scala/scala/blob/v2.12.3/src/library/scala/Function2.scala). See its declaration (ignore the `@specialized` annotation): ``` trait Function2[-T1, -T2, +R] extends ... ``` What are the consequences of using covariance and contravariance? For instance for the function: ``` val f: Function2[Animal, Cat, Dog] = ... ``` Which of these casts are illegal? ``` val f1: Function2[Animal, Cat, Animal] = f val f2: Function2[Dog, Cat, Dog] = f val f3: Function2[Cat, Cat, Dog] = f val f4: Function2[Animal, Cat, Animal] = f val f5: Function2[Animal, Cat, Cat] = f val f6: Function2[Animal, Dog, Dog] = f ``` ## Summary This is the result of applying variances: ## References See the [official Scala documentation on variance](https://docs.scala-lang.org/tour/variances.html). # Enjoy!