// Generic Function but not work as expecting
inline fun <reified C : Collection<T>, T> C.dropElements(word: T): C {
return when (this) {
is Set<*> -> (this - word) as C
is List<*> -> filter { it != word } as C
else -> throw Exception("I can't implement all out of Collection types")
}
}
fun main() {
val originalList: List<String> = readln().split(" ")
val originalSet: Set<String> = originalList.toSet()
val word: String = readln()
val dropElements1: List<String> = originalList.dropElements(word).also(::println)
val dropElements2: Set<String> = originalSet.dropElements(word).also(::println)
// Incorrect: Int and String are different types
val dropElements3: List<Int> = listOf(1, 2, 3).dropElements(word).also(::println)
}
CodePudding user response:
Is the question about the fact that the following line compiles?
listOf(1, 2, 3).dropElements(word)
If so, then what the compiler is doing is inferring these types:
listOf(1, 2, 3).dropElements<List<Int>, Any>(word)
This is possible because the type parameter in List
is covariant, i.e. it is defined as List<out E>
. This means that a List<Int>
is also a List<Any>
.
Doc about generics and variance here.
CodePudding user response:
Your function is working as I would expect.
I think you are expected the integers in dropElements3
to reduce with word
, but the problem is that readln()
is returning a String, so an integer is not matching the String representation of the same Here is your original code (using kotest library to assert the answers)
import io.kotest.matchers.shouldBe
import org.junit.jupiter.api.Test
class ATest {
inline fun <reified C : Collection<T>, T> C.dropElements(word: T): C {
return when (this) {
is Set<*> -> (this - word) as C
is List<*> -> filter { it != word } as C
else -> throw Exception("I can't implement all out of Collection types")
}
}
@Test
fun main() {
val originalList: List<String> = listOf("fish","dog","cat","bird")
val originalSet: Set<String> = originalList.toSet()
var word = "cat"
val dropElements1: List<String> = originalList.dropElements(word).also(::println)
dropElements1 shouldBe listOf("fish","dog","bird")
val dropElements2: Set<String> = originalSet.dropElements(word).also(::println)
dropElements2 shouldBe listOf("fish","dog","bird")
var dropElements3: List<Int> = listOf(1, 2, 3).dropElements(word).also(::println)
dropElements3 shouldBe listOf(1, 2, 3)
word = "2"
dropElements3 = listOf(1, 2, 3).dropElements(word).also(::println)
dropElements3 shouldBe listOf(1, 2, 3) // integer 2 != "2" no dropped elements
var word2 = 2 // now this is an integer
dropElements3 = listOf(1, 2, 3).dropElements(word2).also(::println)
dropElements3 shouldBe listOf(1, 3)
}
}
The List
filter
and Set
-
operations are removing an object based on a equality test on members (and hashcode too for the Set). How can Kotlin/Java know you want to treat integers as like Strings?
The only way you can solve this is to decide how to transform integers into strings (or visa versa). Of course there are multiple string representations of integers - decimal, hexadecimal, and so on...