Home > Enterprise >  How can I make a generic function that works for all subclasses of a collection and at the same time
How can I make a generic function that works for all subclasses of a collection and at the same time

Time:10-24

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

  • Related