Как ограничить параметр функции расширения 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)
работают хорошо и отфильтровывают вызовы с нестроками.