Zhuinden / guide-to-kotlin

[GUIDE] This tutorial assumes all you know is Java, but you want to learn Kotlin.
https://github.com/Zhuinden/guide-to-kotlin/wiki
Apache License 2.0
1.29k stars 99 forks source link

Generics (make <*> and in/out clearer) #5

Open CanOrhan opened 5 years ago

CanOrhan commented 5 years ago

Do you think it'd be a good idea to have a section on generics in Kotlin?

Zhuinden commented 5 years ago

I actually kinda do https://github.com/Zhuinden/guide-to-kotlin/wiki/2.)-Basic-Kotlin-Features#generics-t-blah-inout-and-star-projection-

But it's not that fleshed out because I use star projection when I can't find another way to do it. 🤔

Also I can't wrap my head around in/out and add them only when the compiler is yelling at me.

So in that sense, I do accept explanations that do not involve the term "covariance" and "contravariance" because nobody really understands those terms (and that is where all confusion comes from).

CanOrhan commented 5 years ago

Ahh sorry! I'd missed that bit. To be honest, I'm on the same boat. I'm not super confident with when to use in/out but there is one part of the official documentation that did help - The two example just before star-projections.

Short snippet: You can project a type with in as well:

fun fill(dest: Array<in String>, value: String) { ... } Array<in String> corresponds to Java's Array<? super String>, i.e. you can pass an array of CharSequence or an array of Object to the fill() function.

Perhaps it'd be worth including those examples as they're very Java-centric?

Zhuinden commented 5 years ago

I get confused because you need to use @JvmSuppressWildcards to prevent automatically adding ? extends ViewModel, and I'm not sure why that happens.

CanOrhan commented 5 years ago

I'm at working reading through https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.jvm/-jvm-suppress-wildcards/index.html. I think it might only apply to out? Luckily, the xmas holiday is coming :D

jmfayard commented 5 years ago

@Zhuinden @CanOrhan

The first step is to understand why variance is important in the first place.

For example this java snippet that use Arrays (who have no variances) is unsafe:

void replace(Number[] numbers) {
  numbers[0] = 1.0f;
}
void test() {
  Integer[] array = new Integer[10];
  replace(array);
  // Our Integer[] array now contains a float!!
}

Take-away: Variance is really important to pass generics to other functions and when you work with collections of generics.

Now to undertand in and out in Kotlin, I ask myself whether the my generic class is a Producer or a Consumeror if it's both.

A producer-only (like List) looks like this:

class Producer<out T >(
    val beverage: T
){
    fun produce() : T {
        return beverage
    }
}

A consumer-only class (like Comparable) look like this

class Consumer<in T> {
    fun consume(t: T) {
        println("Drinking $t!")
    }
}

Some class like MutableList are both a Producer and a Consumer

class ProducerAndConsumer<T: Any> {
    lateinit var value: T
    fun updateValue(newValue: T) {
        value = newValue
    }
    fun useValue() { 
        println(value)
    }
}

MutableList is an example that is both a Producer and a Consumer. In this case, the compiler won't let you assign a MutableList<A> to a MutableList<B> no matter what, for your own safety.

A complete example here

class Producer<out T : Beverage>(
    val beverage: T
){
    fun produce() : T {
        return beverage
    }
}
class Consumer<in T: Beverage> {
    fun consume(t: T) {
        println("Drinking $t!")
    }
}

class BadProducer<T: Beverage>( 
    val beverage: T
) {
    fun produce() : T {
        return beverage
    }
}
class BadConsumer<T: Beverage> {
    fun consume(t: T) {
        println("Drinking $t!")
    }
}

interface Beverage
interface Alcohol: Beverage
object Coffee : Beverage
object Vodka: Alcohol
object Whisky: Alcohol

fun main(args: Array<String>) {

    // OUT Generics
    val colombia: Producer<Coffee> = Producer(Coffee)
    val producer: Producer<Beverage> = colombia
    // real-world example
    val drinkWithModeration: List<Alcohol> = listOf(Whisky, Vodka)
    val alcohols : List<Beverage> = drinkWithModeration

    // without OUT Generics
    val someOtherCountry : BadProducer<Coffee> = BadProducer(Coffee)
    val badProducer: BadProducer<Beverage> = someOtherCountry // Compile ERROR: Type Mismatch
    // real-world example
    val drinkWithModeration2: MutableList<Alcohol> = mutableListOf(Whisky, Vodka)
    val alcohols2 : MutableList<Beverage> = drinkWithModeration2 // Compile ERROR: Type Mismatch

    // IN Generics
    val someone = Consumer<Beverage>()
    val me: Consumer<Coffee> = someone

    // without IN Generics
    val someFriend = BadConsumer<Beverage>()
    val peter: BadConsumer<Vodka> = someFriend // Compile ERROR: Type Mismatch

}