Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I constrain a Kotlin extension function parameter to be the same as the extended type?

I want to write an extension method on a generic type T, where the matched type constrains a method parameter.

I want this to compile:

"Hello".thing("world")

But not this, as 42 is not a String:

"Hello".thing(42)

This definition doesn’t work, because T is satisfied by Any

fun <T> T.thing(p: T) {}
like image 700
Duncan McGregor Avatar asked Sep 20 '16 14:09

Duncan McGregor


People also ask

Are Kotlin extension functions static?

Kotlin extensions ,while they claim to be statically dispatched , they feel like dynamically "attached" , if there is such a thing, or late bounded to the instance.

How do I extend a function in Kotlin?

For doing this we create an extension function for MutableList<> with swap() function. The list object call the extension function (MutableList<Int>. swap(index1: Int, index2: Int):MutableList<Int>) using list. swap(0,2) function call.

What is true about extension functions in Kotlin?

Extension functions are a cool Kotlin feature that help you develop Android apps. They provide the ability to add new functionality to classes without having to inherit from them or to use design patterns like Decorator.

What is type parameter in Kotlin?

The type parameter lets you specify exactly that—instead of “This variable holds a list,” you can say something like “This variable holds a list of strings.” Kotlin's syntax for saying “a list of strings” looks the same as in Java: List<String> . You can also declare multiple type parameters for a class.


3 Answers

As mentioned by @Alexander Udalov it's not possible to do directly but there's a workaround where you define the extension method on another type like so:

data class Wrapper<T>(val value: T)

val <T> T.ext: Wrapper<T> get() = Wrapper(this)

fun <T> Wrapper<T>.thing(p: T) {
    println("value = $value, param = $p")
}

With the above the following compiles:

"abc".ext.thing("A")

but the next fails

"abc".ext.thing(2)

with:

Kotlin: Type inference failed: Cannot infer type parameter T in fun <T> Wrapper<T>.thing(p: T): Unit
None of the following substitutions
receiver: Wrapper<String>  arguments: (String)
receiver: Wrapper<Int>  arguments: (Int)
can be applied to
receiver: Wrapper<String>  arguments: (Int)

As suggested by @hotkey it seems that it should be possible to avoid the need for explicit Wrapper type with the following extension property:

val <T> T.thing: (T) -> Any? get() = { println("extension body") }

And then use it as "abc".thing("A") but it also fails. Surprisingly the following does compile "abc".thing.invoke("A")

like image 151
miensol Avatar answered Oct 16 '22 10:10

miensol


As far as I know, this is not possible in Kotlin 1.0. There are several issues in the tracker (first, second) about a similar use case, and the solution proposed in the first one is likely going to help here in the future as well.

like image 10
Alexander Udalov Avatar answered Oct 16 '22 10:10

Alexander Udalov


Improving @miensol's workaround and making it visually identical to function call:

val <T> T.foo: (T) -> SomeType get() = { other -> ... }

This is an extension property that provides a lambda, which can be immediately called with an argument of the same type T like this:

"abc".foo(1) // Fail
"abc".foo("def") // OK

Unfortunately, there seems to be a bug in the compiler which prevents you from writing "abc".thing("abc"), but either of "abc".thing.invoke("abc") and ("abc".thing)("abc) work well and filter out calls with non-strings.

like image 5
hotkey Avatar answered Oct 16 '22 11:10

hotkey