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() {

}

 

Covariance in Kotlin

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

fun main() {
    val shortList: List<Short> = listOf(1, 2, 3, 4, 5)
    convertToInt5(shortList) // All fine here

    val shortList2: MutableList<Short> = mutableListOf(1, 2, 3, 4, 5)
//    convertToInt6(shortList2) // Not allowed - type mismatch. But why? Surely Short is a sub-type (N.B. NOT a sub-class) of Number? And why did it work with an immutable List but not a mutable one?
    // List is a class, but List<String> is a type. Although we rationally would assume that it should accept a List<Short> if it accepts a List<Number>, it is not the same as a subclass.
    // So we want List<Short> to be a subtype of List<Number>. This is where the covariant keyword comes in - it preserves subtyping when working with Generics.
    // When looking at the Collections interface we can see that immutable Collections are covariant (<out E>) whereas mutable Collections are not - hence why the immutable List worked.

}

fun convertToInt5(collection: List<Number>) {
    for (num in collection) {
        println("${num.toInt()}")
    }
}

fun convertToInt6(collection: MutableList<Number>) {
    for (num in collection) {
        println("${num.toInt()}")
    }
}

fun tendGarden(roseGarden: Garden<Rose>) {
    waterGarden(roseGarden) // By default wants a garden<Flower> and we are passing it a Garden<Rose>, even though Rose is a subclass of Flower
    // This is because the Garden class is invariant, so will only accept Garden<Flower>. When we add 'out' to the Garden class this is resolved, but at a price:
    // We're now restricted and can only use the covariant class in the 'out' position, like an immutable Collection (can read but not add)
    // Function parameters are considered in the 'in' position (invariant) and the function return type is in the 'out' position (covariant)
    // Constructor parameters don't have 'in' or 'out' positions so you can always pass in a covariant class. No danger in this situation.
    // But if you pass in a var of type <T> to a constructor, it can't be covariant because setter needs to be generated. It would need to be invariant or declared as a val.
    // If you have a private function or property you don't need to worry about 'in' and 'out' - they're safe inside the class.
}

fun waterGarden(garden: Garden<Flower>) {

}

open class Flower {

}

class Rose: Flower() {

}

class Garden<out T: Flower> { // We can have a garden of daisies, daffodils etc. 'Out' keyword added to make it covariant.
  // As is done within the declaration is known as 'declaration-site variance'. Java only has 'use-site variance' (see post of same name).

    val flowers: List<T> = listOf()

    fun pickFlower(i: Int): T = flowers[i] // Pick the ith flower. Being overly verbose for readability
//    fun plantFlower(flower: T) {} // Not allowed as T is in 'out' position, not suitable as a parameter. This is to stop e.g. a daisy being planted in a rose garden.
    // This applies to member functions of the covariant class only.
}

// If you are ABSOLUTELY sure you will not pass an invalid type to your invariant class you can suppress the compiler warning by using the <@UnsafeVariance T> annotation.