I'm trying to create an extension function I can apply to collections similar to filter/map/etc. Typing due to generics is pretty loose and I want to be more strict. Is there anything I can do in Kotlin?
inline fun <T> Iterable<T>.filterIn(other: Collection<T>): List<T> {
return this.filter { it in other }
}
inline fun <T, R> Iterable<T>.filterIn(other: Collection<R>, transform: (T) -> (R)): List<T> {
return this.filter { transform(it) in other }
}
The following tests pass as expected:
listOf("1", "2", "3").filterIn(setOf("1", "2")) == listOf("1", "2")
listOf("1", "2", "3").filterIn(setOf(1, 2)) { it.toInt() } == listOf("1", "2")
The following tests also pass but I would ideally want them to fail at compile time:
listOf("1", "2", "3").filterIn(setOf(1, 2)) == emptyList<String>() // should know set not same type as list
listOf("1", "2", "3").filterIn(setOf(1, 2)) { it } == emptyList<String>() // should know function doesn't return same type as list
listOf("1", "2", "3").filterIn(setOf(1, 2)) { mapOf("foo" to "bar") } == emptyList<String>() // should know function doesn't return same type as list
I believe this is not officially supported as of Kotlin 1.9.22.
However, there is a hidden feature that does exactly this. We can enable it by annotating type parameters with @kotlin.internal.Exact
. Unfortunately, as its name suggests, it is internal and we can't use it just like that. One way to use it is:
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
inline fun <T> Iterable<@kotlin.internal.Exact T>.filterIn(other: Collection<T>): List<T> {
return this.filter { it in other }
}
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
inline fun <T, R> Iterable<@kotlin.internal.Exact T>.filterIn(other: Collection<R>, transform: (T) -> (@kotlin.internal.Exact R)): List<T> {
return this.filter { transform(it) in other }
}
Of course, we don't have any guarantees it will work in future versions of Kotlin.