1
// 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)
}
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
  • 1
    Interesting idea, but I don't think it's possible, I'm afraid. For example, you could create a class implementing Collection that had no public constructors, and instead had a public factory method in its companion object. There's no way an unrelated class could find that (without being told). – gidds Oct 23 '22 at 21:06

3 Answers3

2

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.

gpunto
  • 2,573
  • 1
  • 14
  • 17
  • Thank you for your quick reply, but...How could I restrict and disallow different types in the function itself. I.e. the type of the collection must be the same type as the item being searched for in it.Even in the function itself, I could not do a check because the type of the collection is the erase.Of course, it would be better to limit it at the beginning of the generic types. – Kaloyan Karaivanov Oct 24 '22 at 06:17
  • I don't think it's possible. One potential solution is to create your collection type as suggested [here](https://stackoverflow.com/questions/59232266/how-to-restrict-the-generic-extension-function-parameter-type) but that probably defies the purpose of the function. – gpunto Oct 24 '22 at 17:07
  • I was actually up to the challenge. I posted the solution above. Thanks for your time. – Kaloyan Karaivanov Oct 25 '22 at 06:03
1

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...

AndrewL
  • 2,034
  • 18
  • 18
0

I think T is a covariant type parameter with the upper bound T: comparable is a great deal!

data class Koko(val name: String) : Comparable<Koko> {
    override fun compareTo(other: Koko) = name.compareTo(other.name)
}

inline fun <reified C : Iterable<T>, reified T : Comparable<T>> C.dropElements(word: T): C {
    return when (this) {
        is Set<*> -> (this - word) as C
        is List<*> -> (this.filter { it != word }).toList<T>() as C
        else -> throw Exception("I can't implement all out of Collection types")
    }
}

fun main() {
    val list: List<String> = listOf("apple", "banana", "orange")
    val set: Set<String> = list.toSet()
    val mutableList: MutableList<String> = list.toMutableList()
    val listOfKoko: List<Koko> = List<Koko>(5) { Koko("Name$it") }
    val mapOfKoko: Map<Int, String> = list.withIndex().associate { it.index to it.value }

    val dropElements1: List<String> = list.dropElements("apple").also(::println)
    val dropElements2: Set<String> = set.dropElements("apple").also(::println)
    val dropElements3: List<Koko> = listOfKoko.dropElements(Koko("Name1")).also(::println)
    val dropElements4: MutableList<String> = mutableList.dropElements("apple").also(::println)

    // Incorrect: different types ot not is Iterable
    val dropElements5: List<String> = list.dropElements(1).also(::println)
    val dropElements6: List<Int> = listOf(1, 2, 3).dropElements("apple").also(::println)
    val dropElements7: Map<Int, String> = mapOfKoko.dropElements(Koko("Name1")).also(::println)
}

enter image description here