Generic with in and out variance


#1

Hi,

I started using Kotlin and it is very nice and compact. I ran into a use case that I am not sure how to use the in/out variance. Let me start with sample code here…

interface Base {
    fun getId(): String
}

interface CarBase : Base {
    fun getMake() : String
}

class Honda : CarBase {
    override fun getId(): String {
        return "1"
    }

    override fun getMake() : String {
        return "Honda"
    }
}

class Toyota : CarBase {
    override fun getId(): String {
        return "1"
    }

    override fun getMake() : String {
        return "Toyota"
    }
}

open class BaseRepo<T> {
    open fun save(t: T): T {
        println("Saving object")
        return t
    }
}

open class CarBaseRepo<in T> : BaseRepo<T>() {
    override fun save(t: T): T {
        println("Saving object")
        return t
    }
}

I would like to make the CarBaseRepo.save accepts and returns the CarBase . Someone could help me to re-write to do that.

Thanks in adv,
-Kuppa


#2

Try this:

class CarBaseRepo() : BaseRepo<CarBase>() {
    override fun save(carbase: CarBase): CarBase {
        println("Saving object")
        return t
    }
}

#3

Also, in kotlin you can use property getters instead of the getters you’re using.
Consider the below code:

interface Base {
    val id: String
}

interface CarBase : Base {
    val make : String
}

class Honda : CarBase {
    override val id get() = "1"
    override val make get() = "Honda"
}

class Toyota : CarBase {
    override val id get() = "1"
    override val make get() = "Toyota"
}

#4

Thanks for the suggestion,

This should work but from caller perspective, user needs to do a cast into appropriate type. Still, I don’t feel like a correct solution.

fun test() {
    val hondaRepo = CarBaseRepo()
    val toyotoRepo = CarBaseRepo()

    val honda: Honda = hondaRepo.save(Honda()) as Honda
    val toyoda: Toyota = toyotoRepo.save(Toyota()) as Toyota
}

#5

Thanks for Kotlin coding style. For demonstration purpose, I use very simple method which happens to be a getter. The real program has different methods. Anyway, I learnt something new here.


#6
open class CarBaseRepo<in T> : BaseRepo<T>() {
    override fun save(t: T): T {
        println("Saving object")
        return t
    }
}

This doesn’t compile because the compiler needs to warrant type safety.
You can refer to this page for a detailed explanation.

A short version is that, when you mark T as in, you say that the class is a “consumer” of T.

consider the following interface:

interface Consumer<in T> {
    fun accept(T val)
}

Because I marked the type parameter T as in, the below assignment is valid:

val consumer: Consumer<Object> = { println(it) }
val stringConsumer: Consumer<String> = consumer

in this example, Consumer<Object> can be assigned to a Consumer<String>, because Consumer<String> is considered a superclass of Consumer<Object>.
In Java, this would have to be written like so:

Consumer<Object> consumer = System.out::println;
Consumer<? super String> stringConsumer = consumer;

Note the use of wildcard types: <? super String>. In kotlin, this declaration is moved to the declaration of the type parameter.

This can be done the other way around using out parameter, so you get the following (producer):

interface Supplier<out T> {
   fun get(): T
}

// In kotlin, you can assign Supplier<String> to Supplier<Object> because of "out"
val stringSupplier: Supplier<String> = { "some string" }
val supplier: Supplier<Object> = stringSupplier

// In java, it looks like this:
Supplier<String> stringSupplier = () -> "some string";
Supplier<? extends Object> supplier = stringSupplier;

In your case of CarBaseRepo,
you’re using the type parameter in both consumer and producer positions:
override fun save(t: T): T
This means that, if it’s marked in, the type T can not be returned from any of the functions.
This is because in means we can go from CarBaseRepo<Object> to CarBaseRepo<String>,
but if we return T from our function save, we are returning Object from save but Object is not assignable to String.

The solution?
use in nor out.

open class CarBaseRepo<T> : BaseRepo<T>() {
    override fun save(t: T): T {
        println("Saving object")
        return t
    }
}

#7

Thank you for the detailed explanation, as per the design (or limitation) we cannot have both supplier and consumer together. In my actual application, I am trying to extend the spring’s repository class.

import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.data.repository.NoRepositoryBean
import org.springframework.data.repository.PagingAndSortingRepository


@NoRepositoryBean
interface AppAwareRepository<A> : PagingAndSortingRepository<A, String> {
    fun findAllByAppId(tenantId: String, pageable: Pageable): Page<A>;
    // There are other methods that are specific to app
}

I can make the generic type A a neither in nor out. Thanks for helping. I keep the generic A as is instead of making either supplier or consumer.

Thanks.