user3681304
user3681304

Reputation: 719

Kotlin generic functions and contravariance

I cannot get my head around a seemingly easy problem. I have Reply class, which can contain different response types - Views:

interface View
data class NiceView(val name: String) : View

class MyReply<T : View>(view: T)

Now, I'd like to write a function that accepts a command and returns an appropriate view based on the command:

fun <T : View> handle(command: Command): Reply<T> {
   return Reply(NiceView(""))
}

I can see two issues here:

  1. I get this weird error on the return type Reply<T>, but this only happens in this toy example and not my production code:

    Type argument is not within its bounds.
    Expected: View
    Found: T
    
  2. Error when returning Reply(). This is the thing that is killing my brain and this is what I'm seeing in my production code:

    Type mismatch.
    Required: T
    Found: NiceView
    Type mismatch.
    Required: Reply<T>
    Found: Reply<NiceView>
    Type mismatch.
    Required: View
    Found: NiceView
    

I did try messing around with co- and contravariance using in and out keywords but to no avail. Could someone point me in the right direction here?

Upvotes: 2

Views: 208

Answers (3)

Adam Millerchip
Adam Millerchip

Reputation: 23091

The problem is that handle() is saying that T is a generic type that is a subclass of View. So T can be any subclass of View, but you are trying to force it to be NiceView. It means that code like this would not work:

val otherReply: Reply<OtherView> = handle(command)

handle is trying to return NiceView, but the generic parameter says it should be OtherView.

If you want to guarantee that your function always returns Reply<T : View>, you need to construct an instance of the generic type T. That means you have to identify the View implementation somehow. I don't know what the rest of your code looks like, but here's an example where it accepts the view as a parameter:

fun <T : View> handle(command: Command, view: T): Reply<T> {
    return Reply(view)
}

fun main() {
    val otherReply: Reply<OtherView> = handle(command, OtherView(""))
}

Or if you don't care about allowing the caller to specify the view type, you don't need the generic parameter at all:

fun handle(command: Command): Reply<View> {
    return Reply(NiceView(""))
}

Upvotes: 1

Louis Wasserman
Louis Wasserman

Reputation: 198033

fun <T : View> handle(command: Command): Reply<T> { does not mean a function that "accepts a command and returns an appropriate view." It means "a function that accepts a type of view -- and a command -- and returns a reply of that type of view." The caller of handle can choose whatever type of View it wants to get, which is not what you want -- nor what you've implemented, since the user might want something other than NiceView.

The appropriate type of the handle function in this code, given your stated goal, is

fun handle(command: Command): Reply<*>

Upvotes: 1

Sergio
Sergio

Reputation: 30645

I couldn't reproduce the first issue, but the second one can be solved by simply adding in modifier to the generic parameter of Reply class or return value of handle function:

interface View
data class NiceView(val name: String) : View

class Reply<in T : View>(view: T)

fun <T : View> handle(command: Command): Reply<T> {
    return Reply(NiceView(""))
}

// OR

class Reply<T : View>(view: T)

fun <T : View> handle(command: Command): Reply<in T> {
    return Reply(NiceView(""))
}

in modifier indicates contravariance (similar to "? super T" in Java).

Upvotes: 1

Related Questions