Как ограничить параметр функции расширения Kotlin таким же, как и расширенный?

Я хочу написать метод расширения для общего типа T, где подобранный тип сдерживает параметр метода.

Я хочу, чтобы это скомпилировалось:

"Hello".thing("world")

Но не это, поскольку 42 не является строкой:

"Hello".thing(42)

Это определение не работает, так как T выполняется Any

fun <T> T.thing(p: T) {}

Ответы

Ответ 1

Как упоминалось @Aldander Udalov, это невозможно сделать напрямую, но есть обходное решение, в котором вы определяете метод расширения для другого типа, например:

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

С приведенными выше компиляторами:

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

но следующий сбой

"abc".ext.thing(2)

с:

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)

Как было предложено @hotkey, кажется, что можно избежать необходимости использования явного типа Wrapper со следующим свойством расширения:

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

И затем используйте его как "abc".thing("A"), но он также не работает. Неожиданно следующее компилирует "abc".thing.invoke("A")

Ответ 2

Насколько я знаю, это невозможно в Kotlin 1.0. В трекере есть несколько проблем (первый, второй) о аналогичном варианте использования, и решение, предложенное в первом, вероятно, будет способствовать и здесь в будущем.

Ответ 3

Улучшение обходного пути @miensol и его визуальное совпадение с вызовом функции:

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

Это свойство расширения, которое предоставляет лямбду, которая может быть немедленно вызвана с аргументом того же типа T следующим образом:

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

К сожалению, там кажется ошибкой в ​​компиляторе, что мешает вам писать "abc".thing("abc"), но либо "abc".thing.invoke("abc") и ("abc".thing)("abc) работают хорошо и отфильтровывают вызовы с нестроками.