Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extension functions for generic classes in Kotlin

Tags:

What's wrong with my extension function below

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

Update

I wonder why it's different than regular extension functions, where T successfully gets inferred as Any and would like to achieve the same behavior, e. g. T to get inferred as Foo<Any>

class Foo {
    fun <T> T.foo(that: T): T = throw Exception()

    init {
        "str" foo 42
    }
}
like image 597
Yaroslav Avatar asked Oct 01 '15 09:10

Yaroslav


People also ask

What are the extension functions in Kotlin?

Kotlin extension function provides a facility to "add" methods to class without inheriting a class or using any type of design pattern. The created extension functions are used as a regular function inside that class. The extension function is declared with a prefix receiver type with method name.

How do you extend a class in Kotlin?

Kotlin provides the ability to extend a class or an interface with new functionality without having to inherit from the class or use design patterns such as Decorator. This is done via special declarations called extensions.

Which of the following extension method are used in Kotlin?

Kotlin with top-level extension function is a pure Java class with a static method. That's why it needs to somehow pass the original String.

What is the extension of Kotlin file?

A source code written in Kotlin is saved with . kt extension which is commonly known as Kotlin file extension. The Kotlin is a general-purpose cross-platform programming language developed by JetBrains to be fully interoperable with Java.


2 Answers

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

like image 58
Andrey Breslav Avatar answered Sep 21 '22 01:09

Andrey Breslav


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.

like image 33
extender Avatar answered Sep 23 '22 01:09

extender