Use-site variance

Use of covariance and contravariance within functions in Kotlin.

fun main() {

    val cars1 = mutableListOf(KotlinCar(), KotlinCar())
    val cars2: MutableList<KotlinCar> = mutableListOf()
    copyCars(cars1, cars2)

    val fords1 = mutableListOf(Ford(), Ford())
    val fords2: MutableList<Ford> = mutableListOf()
//    copyCars(fords1, fords2) // Wants a MutableList<KotlinCar>, as consistent with covariance/invariance. Once again we don't want to have to write a copyCars() function for every type of car.
    copyCars2(fords1, fords2) // Works because copyCars2 is covariant
//    copyCars2(fords1, cars2) // Doesn't initially work because in each case <T> is different (Ford and KotlinCar)
    val cars3: MutableList<KotlinCar> = mutableListOf(Ford(), Ford()) // Yet we should be able to as we can do this <-
    // If we look at our copyCars function we can see that we only read from 'source' and only write to 'destination', so we can use co- and contravariance
    copyCars3(fords1, cars1) // It works!
}

fun copyCars(source: MutableList<KotlinCar>, destination: MutableList<KotlinCar>) {
    for (car in source) {
        destination.add(car)
    }
}

fun <T> copyCars2(source: MutableList<T>, destination: MutableList<T>) {
    for (car in source) {
        destination.add(car)
    }
}

fun <T> copyCars3(source: MutableList<out T>, destination: MutableList<T>) { // Co- or contravariance declared in function - use-site variance, also known as type projection. Can also be used with function return types
// fun <T> copyCars3(source: MutableList<T>, destination: MutableList<in T>) { // This would also work
    for (car in source) {
        destination.add(car)
    }
}

open class KotlinCar {

}

class Toyota: KotlinCar() {

}

class Ford: KotlinCar() {

}

 

Contravariance

Understanding how contravariance affects the ability to use Generics in Kotlin.

fun main() {
    val roseTender = object: FlowerCare2<Rose2> {
        override fun prune(flower: Rose2) {
            println("I'm pruning a rose!")
        }
    }

    val daffodilTender = object: FlowerCare2<Daffodil2> {
        override fun prune(flower: Daffodil2) {
            println("I'm pruning a daffodil!")
        }
    }

    val roseGarden = Garden2(listOf(Rose2("Rosemary"), Rose2("Rosie")), roseTender)
    println(roseGarden.javaClass)

    val daffodilGarden = Garden2(listOf(Daffodil2(), Daffodil2(), Daffodil2()), daffodilTender)
    println(roseGarden.javaClass)

    roseGarden.tendFlower(0)

    daffodilGarden.tendFlower(2) // This is rather repetitive. We have to create Gardens and call tendFlower, but roseTender and daffodilTender are doing the same thing

    val flowerTender = object : FlowerCare2<Flower2> { // Create a generic flowerTender that can handle any type of Flower2
        override fun prune(flower: Flower2) {
            println("I'm tending a specifically-named ${flower.name}!")
        }
    }

    val roseGarden2 = Garden2(listOf(Rose2("Rosemary"), Rose2("Rosie")), flowerTender) // Won't work without adding 'in' to FlowerCare2 class, as expects FlowerCare2<Rose2>. We need <T> matching to be more relaxed.
    // Covariance preserves the subtyping. Contravariance is the opposite - starting with subclass and wanting to accept superclasses.
    // Comes at a price. We can only write them and not read from them. Cannot use them as return types from functions.

    roseGarden2.tendFlower(1)

}

class Garden2<T: Flower2>(val flowers: List<T>, val flowerCare: FlowerCare2<T>) {
    fun pickFlower(i: Int) = flowers[i]
    fun tendFlower(i: Int) {
        flowerCare.prune(flowers[i])
    }
}

open class Flower2(val name: String) {

}

class Rose2(val roseName: String): Flower2(roseName) {

}

class Daffodil2: Flower2("Daffodil") {

}

interface FlowerCare2<in T> { // Made contravariant by 'in' keyword. Will accept T or any superclass of.
  // As it is done in the declaration is known as 'declaration-site variance'. Java only has 'use-site variance' (see post of same name).
    fun prune(flower: T)
//    fun pick(): T // Cannot return this Generic as it is now contravariant
}