Last active
April 8, 2020 05:16
-
-
Save stumash/6defbea2586b9d9b96c1a96115b3ed57 to your computer and use it in GitHub Desktop.
scala covariance and contravariance
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import scala.collection.mutable.ArrayDeque | |
object Co_and_Contra_Variance { | |
def main(args: Array[String]): Unit = { | |
theBasicIdea() // - What covariance and contravariance let you do, without discussing why or how | |
theCoreConceptIsBasedOnFunctions() // - How do we decide if function f1 can be substituted by f2? (aka f2 is subtype of f1) | |
bringItAllTogether() // - Now that we know why and how, let's see how it applies | |
} | |
def theBasicIdea(): Unit = { | |
class A() | |
class B() extends A | |
class C() extends B | |
// This class is covariant (+T) with respect to its generic type parameter T, so you | |
// can use instances of MyCovariantBox[SubclassOfT] wherever a MyCovariantBox[T] is required | |
class MyCovariantBox[+T]() | |
def useMyCovariantBox(covbox: MyCovariantBox[B]): Unit = println("got a box of B (or sub B)") | |
useMyCovariantBox(new MyCovariantBox[A]()) // ERROR: type mismatch: expected MyCovariantBox[B] | |
useMyCovariantBox(new MyCovariantBox[B]()) | |
useMyCovariantBox(new MyCovariantBox[C]()) | |
// This class is contravariant (-T) with respect to its generic type paramter T, so you | |
// can use instances of MyContravariantBox[SuperclassOfT] wherever a MyContravariantBox[T] is required | |
class MyContravariantBox[-T]() | |
def useMyContravariantBox(contrabox: MyContravariantBox[B]): Unit = println("got a box of B (or super B)") | |
useMyContravariantBox(new MyContravariantBox[A]()) | |
useMyContravariantBox(new MyContravariantBox[B]()) | |
useMyContravariantBox(new MyContravariantBox[C]()) // ERROR: type mismatch: expected MyContravariantBox[B] | |
} | |
def theCoreConceptIsBasedOnFunctions(): Unit = { | |
class Organism() { def beOrganism(): Unit = println("I'm alive") } | |
class Animal() extends Organism { def beAnimal(): Unit = println("I'm an animal") } | |
class Dog() extends Animal { def woof(): Unit = println("I'm saying woof") } | |
class Cat() extends Animal { def meow(): Unit = println("I'm saying meow") } | |
val animals: ArrayDeque[Animal] = ArrayDeque(new Animal(), new Dog(), new Cat()) | |
def makeAnimalAndAppend(animals: ArrayDeque[Animal], makeAnimal: (() => Animal)): Unit = | |
animals.append( makeAnimal() ) | |
// So, what kind of functions match the type signature of makeAnimal? | |
makeAnimalAndAppend(animals, (() => new Organism())) // ERROR | |
makeAnimalAndAppend(animals, (() => new Animal())) | |
makeAnimalAndAppend(animals, (() => new Dog())) | |
// So, for functions of type F:(anything => T), functions of type F':(anything => SubclassOfT) are subtypes of F. | |
// This means that functions are covariant with respect to their return type | |
// | |
// The intuitive explanation is that if F returns T, the calling code is expecting a T, so it can | |
// receiving SubclassOfT instead, but not SuperclassOfT | |
def popAnimalAndUse(animals: ArrayDeque[Animal], useAnimal: (Animal => Unit)): Unit = | |
useAnimal( animals.removeLast() ) | |
// So, what kind of functions match the type signature of useAnimal? | |
popAnimalAndUse(animals, ((o: Organism) => o.beOrganism())) | |
popAnimalAndUse(animals, ((a: Animal) => a.beAnimal())) | |
popAnimalAndUse(animals, ((d: Dog) => d.woof())) // ERROR | |
// So, for functions of type F:(T => anything), functions of type F':(SuperclassOfT => anything) are subtypes of F. | |
// This means that functions are contravariant with respect to argument type | |
// | |
// The intuitive explanation is that if F accepts args of type T, the calling code is expecting to be able to pass a T. | |
// If that code uses F' instead, it can still pass a T to F' | |
// The general idea that functions that 'give a T' can be safely replaced with functions that 'give a SubclassOfT', | |
// and functions that 'use a T' can be safely replaced with functions that 'use a SuperclassOfT' | |
// So, the Scala Programming Language defines the type for functions like this: | |
// | |
// Function[-A1, -A2, -A3, ..., -AN, +R] | |
// | |
// make sense? | |
} | |
def bringItAllTogether(): Unit = { | |
class Organism() { def beOrganism(): Unit = println("I'm alive") } | |
class Animal() extends Organism { def beAnimal(): Unit = println("I'm an animal") } | |
class Dog() extends Animal { def woof(): Unit = println("I'm saying woof") } | |
class Cat() extends Animal { def meow(): Unit = println("I'm saying meow") } | |
// 1. | |
// Why can't types that are covariant +T have methods with arguments of type T : | |
abstract class CovWrapper[+T]() { | |
def useT(t: T): Unit // the compiler complains here, because if it didn't, you could do this: | |
} | |
def useAnimalCovWrapper(wa: CovWrapper[Animal]): Unit = wa.useT(new Animal()) | |
val wd: CovWrapper[Dog] = new CovWrapper[Dog]() { def useT(dog: Dog): Unit = dog.woof() } | |
// You should be able to use CovWrapper[Dog] instead of CovWrapper[Animal] | |
useAnimalCovWrapper(wd) // But you can't do wd.useT(new Animal()), because wd calls dog.woof() ! | |
// 2. | |
// Why can't contravariant types -T have methods that return the type T : | |
abstract class ContraWrapper[-T]() { | |
def giveT(): T // compiler complains here, because if it didn't, you could do this: | |
} | |
def useAnimalContraWrapper(wa: ContraWrapper[Animal]): Unit = wa.giveT().beAnimal() | |
val wo: ContraWrapper[Organism] = new ContraWrapper[Organism]() { def giveT(): Organism = new Organism() } | |
// You should be able to use ContraWrapper[Organism] instead of ContraWrapper[Animal] | |
useAnimalContraWrapper(wo) // But you can't do wo.giveT().beAnimal(), because wo.giveT() returns Organism ! | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment