Функция расширения Kotlin с универсальными типами, зависящими от параметра типа получателя (без указания его явно) - PullRequest
1 голос
/ 27 мая 2019

Иногда я хочу иметь функцию расширения для универсального типа, чей параметр или возвращаемое значение является подтипом данного параметра типа, но без необходимости указывать этот фактический параметр типа. (может быть, если это не понятно, код прояснит это лучше?)

Используя подпись этот ответ :

inline fun <reified U : T, T> Iterable<T>.partitionByType(): Pair<List<U>, List<T>>

Это заставляет меня использовать его следующим образом:

val list : List<SomeType>
list.partitionByType<InterestedType, SomeType>()

Тем не менее, я хочу написать следующее:

list.partitionByType<InterestedType>()

Где InterestedType является подтипом параметра типа списка, т.е. SomeType. Оба решения выше должны затем дать мне Pair<List<InterestedType>, List<SomeType>> в качестве возвращаемого значения.

Это, однако, на самом деле не возможно. Лучшее, что приходит мне в голову - это подпись, похожая на следующую:

 inline fun <reified U : Any> Iterable<Any>.partitionByType(): Pair<List<U>, List<Any>>

Но тогда мы теряем фактический тип T / SomeType, который был известен иначе. Непроверенный актерский состав все еще возможен на стороне вызывающей стороны, но это не то, что я хочу.

Так что мой вопрос: это как-то возможно? Имея что-то вроде U : T, без указания T? Или что-то запланировано в этом отношении (имеется в наличии KEEP или Kotlin)?

Для меня это звучит как разумная особенность, по крайней мере, до тех пор, пока она применяется только к функции расширения. Если это не так, то с какими проблемами я, вероятно, сейчас просто не обращаю внимания? То, что я на самом деле не ищу, - это обходной путь (например, наличие промежуточного типа), но, возможно, оно того стоит, чтобы иметь его в качестве ответа.

Чем больше я об этом думаю. Разве не было бы правильнее, если бы функции расширения для универсальных типов были бы объявлены следующим образом?

fun SomeType<T>.extended()

вместо

fun <T> SomeType<T>.extended()

Потому что: если бы я контролировал SomeType<T> и добавил бы эту функцию туда, мне не потребовалась бы какая-либо информация общего типа для ее объявления, например, тогда будет достаточно следующего:

fun extended()

То же самое в отношении функций с собственными родовыми типами, например ::1010 *

fun <U : T> extended2()

Добавление этого в качестве функции расширения теперь заставляет добавить T -генерический тип, хотя тип, к которому мы хотели бы применить его, явно SomeType<T>:

fun <T, U: T> SomeType<T>.extended2()

Ответы [ 2 ]

1 голос
/ 27 мая 2019

Другое не совсем элегантное решение, которое может считаться даже хуже, чем пропуск type<T>(), так как оно не универсально и требует шаблон для каждой такой функции, - это возвращать объект-обертку (что также бессмысленно).

Однако, с использованием классов inline, он может быть реализован без каких-либо накладных расходов во время выполнения, поэтому я все равно опубликую его здесь:

fun main() {
    println(listOf<Number>(1, 2.0, 3f).doPartition.byType<Int>())
}

inline class PartitionByTypeWrapper<T>(val list: List<T>) {
    inline fun <reified U : T> byType(): Pair<List<U>, List<T>> {
        val (us, ts) = list.partition { it is U }
        @Suppress("UNCHECKED_CAST")
        return us as List<U> to ts
    }
}

inline val <T> List<T>.doPartition get() = PartitionByTypeWrapper(this)
1 голос
/ 27 мая 2019

Внимание: это самоответ только для «промежуточного типа» -solution-part. Я все еще ищу ответ, если он может быть решен без такого обходного пути или если есть планы поддержать его.

Пример промежуточного типа для решения этой проблемы может выглядеть следующим образом:

class PartitionType<T>

inline fun <reified T> type() = PartitionType<T>()
inline fun <reified U : T, T> Iterable<T>.partitionBy(type: PartitionType<U> = type()): Pair<List<U>, List<T>> {
    val first = ArrayList<U>()
    val second = ArrayList<T>()
    for (element in this) {
        if (element is U) first.add(element)
        else second.add(element)
    }
    return Pair(first, second)
}

Так можно написать:

list.partitionBy(type<InterestedType>())

, что приведет к значению Pair<List<InterestedType>, List<SomeType>>-return, но требует добавления того типа, который не будет использоваться в любом случае (кроме косвенного; здесь может иметь смысл inline class, но ... каким должен быть параметр тогда ?).

Здесь ключ состоит в том, чтобы просто передать заинтересованный тип (ы) в качестве параметра вместо указания его в качестве запрошенного универсального типа (partitionBy<InterestedType, SomeType>() все равно будет работать (или, более правильно: все равно будет необходимо)).

Может быть, я использовал неправильные слова, и поэтому не смог найти ничего подходящего. Или, может быть, это то, что я хочу иметь, и больше никого? По крайней мере, для меня это звучит так, как будто это может / должно работать.

Другой вариант - явно указать все функции расширения для типов, которые уже известны, но тогда нам нужно знать о конфликтах платформы, например ::

@JvmName("partitionSomeTypeByType") // only needed if we require more partitionByType-functions with differing generic type parameters
inline fun <reified U : SomeType> Iterable<SomeType>.partitionByType() = partitionBy<U, SomeType>()

Но опять-таки только обходной путь.

...