Kotlin extension function with generic types dependant on the receiver type parameter (without specifying it explicitly)
Asked Answered
B

2

7

Sometimes I want to have an extension function on a generic type, whose parameter or return value is a subtype of the given type parameter, but without having to specify that actual type parameter. (maybe, if it isn't clear, the code will clarify it better?)

Using the signature of this answer:

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

It forces me to use it as follows:

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

However, what I rather want to write is the following:

list.partitionByType<InterestedType>()

Where InterestedType is a subtype of the type parameter of the list, i.e. SomeType. Both solutions above should then give me Pair<List<InterestedType>, List<SomeType>> as a return value.

This however isn't really possible. The best that comes to my mind is a signature similar as to follows:

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

But then we lose the actual type T/SomeType which was known otherwise. An unchecked cast is still possible on the caller side, but that's not what I am after.

So my question is: is this somehow possible? Having something like U : T in place, without specifying the T? Or is there anything planned in this regard (KEEP or Kotlin issue available)?

For me it sounds like a reasonable feature, at least as long as it is applied on an extension function only. If it's not, what would be the problems of this that I am probably just overlooking/ignoring at the moment? What I am not really looking for is a workaround (like having an intermediate type), but it may be worth it to have it as answer as well.

The more I think of it. Wouldn't it be more correct, if extension functions on generic types would be declared as follows?

fun SomeType<T>.extended()

instead of

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

Because: if I were under control of SomeType<T> and would add that function there, I wouldn't need any generic type information to declare it, e.g. the following would suffice then:

fun extended()

Same regarding functions with own generic types, e.g.:

fun <U : T> extended2()

Adding that as extension function now forces one to add that T-generic type, although the type on which we would like to apply it, is clearly SomeType<T>:

fun <T, U: T> SomeType<T>.extended2()
Bailly answered 27/5, 2019 at 14:18 Comment(0)
B
1

Attention: this is a self-answer only for the "intermediate type"-solution-part. I am still looking for an answer if it can be solved without such a workaround or if there are plans to support it.

Example of an intermediate type to solve that issue could look as follows:

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

This way one can write:

list.partitionBy(type<InterestedType>())

which will result in the Pair<List<InterestedType>, List<SomeType>>-return value, but requires to add that type that will not be used anyhow (except indirectly; an inline class may make sense here, but ... what should the parameter be then?).

The key here is to just pass the interested type(s) as parameter instead of specifying it as a requested generic type (partitionBy<InterestedType, SomeType>() would still work (or more correctly: would still be needed)).

Maybe I used the wrong words and that's why I couldn't find anything suitable yet. Or maybe it's just something I want to have and no one else? At least for me it sounds as if it could/should work.

Another variant is to explicitly specify all the extension functions on types that are known already, but then we need to be aware of platform clashes, e.g.:

@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>()

But again only a workaround.

Bailly answered 27/5, 2019 at 15:15 Comment(0)
H
1

Another not nearly elegant solution, which may be considered even worse than passing a type<T>() as it isn't universal and requires boilerplate for each such a function, is to return a wrapper object (that is similarly meaningless).

However, with the use of inline classes, it can be implemented without any runtime overhead, so I'll post it here anyway:

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)
Hardden answered 27/5, 2019 at 16:58 Comment(1)
hmm... the inline class should probably also be applied for my PartitionType-class... (but what should the parameter be then... KClass<T> and T: Any? some other wrapper type just to deliver the generic type? don't know... I will skip it for the moment :-))Bailly

© 2022 - 2024 — McMap. All rights reserved.