What\'s wrong with my extension function below
class Foo {
fun Foo.plus(that: Foo): Foo = throw Exception()
Your method plus
expects the parameter to have the same generic type parameter T
as the receiver. Hence, you can't add a Foo<String>
to a Foo<Int>
.
If you want to be able to add all types of Foo
, than you need to declare your extension function like so:
operator fun <T,R> Foo<T>.plus(that: Foo<R>): Foo<T> = throw Exception()
I think the accepted answer by Andrey Breslaw is correct, but provides incorrect solution.
The compiler simply needs to be told to infer common supertype for the generic type arguments supplied, i.e., as long as the generic type arguments of Foo share a common supertype (and they always will), use it. Like:
operator fun <T, R: T, S: T> Foo<R>.plus(that: Foo<S>): Foo<T> = throw Exception()
Now the resulting generic type argument of returned Foo will be widened as necessary if the types do not match, but the operation itself is legal, without introducing covariance.
The issue is at the very heart of how generics work.
class Foo {
fun <T> T.foo(that: T): T = throw Exception()
init {
"str" foo 42
}
}
This works, because the compiler can find a T
that fits both the function signature and the arguments: it is Any
, and the function is turned into this one:
fun Any.foo(that: Any): Any = ...
Now, String
is a subtype of Any
, Int
is a subtype of Any
, so this function is applicable to the arguments.
But in your first example:
class Foo<T> {
fun <T> Foo<T>.plus(that: Foo<T>): Foo<T> = throw Exception()
init {
Foo<Int>() + Foo<String>() // A receiver of type Foo<T> is required
}
}
It's all different. There's no such T
. Let's be naïve and try Any
:
fun Foo<Any>.plus(that: Foo<Any>): Foo<Any> = ...
Now, Foo
is invariant in T
, so Foo<Int>
is not a subtype of Foo<Any>
, and in fact there's no type T
other than Int
that would make Foo<T>
a supertype of Foo<Int>
. So, T
must be exactly Int
, but it also must be exactly String
by the same logic (because of the second argument), so there's no solution, and the function is not applicable.
You could make it work by making Foo
co-variant in T
:
class Foo<out T> {
fun <T> Foo<T>.plus(that: Foo<T>): Foo<T> = throw Exception()
init {
Foo<Int>() + Foo<String>() // A receiver of type Foo<T> is required
}
}
This imposes some limitations on possible signatures of members of Foo
, but if you are OK with them, it fixes your issue.
Have a look at this link for more details: http://kotlinlang.org/docs/reference/generics.html