guide-to-kotlin icon indicating copy to clipboard operation
guide-to-kotlin copied to clipboard

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

Open CanOrhan opened this issue 6 years ago • 5 comments

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

CanOrhan avatar Dec 20 '18 15:12 CanOrhan

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).

Zhuinden avatar Dec 20 '18 16:12 Zhuinden

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?

CanOrhan avatar Dec 21 '18 08:12 CanOrhan

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

Zhuinden avatar Dec 21 '18 13:12 Zhuinden

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

CanOrhan avatar Dec 21 '18 14:12 CanOrhan

@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
    
    
}

jmfayard avatar Jan 02 '19 10:01 jmfayard