Reputation: 719
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:
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
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
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
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
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