How can I use vararg with different generics in Kotlin?

眉间皱痕 提交于 2020-07-22 10:08:26

问题


I want to use vararg with generics with different types of each arguments

what I've already tried:

class GeneralSpecification<T> {
    fun <P> ifNotNullCreateSpec(vararg propToCreateFun: Pair<P?, (P) -> Specification<T>>): List<Specification<T>> = 
       propToCreateFun.mapNotNull { (prop, funCreateSpec) ->
            prop?.let(funCreateSpec)
    }
...
}

but I can't use this like:

ifNotNullCreateSpec("asdf" to ::createStringSpec, 5 to ::createIntSpec)

(different types in vararg pairs)

How can I use vararg with different generics, when I need to restrict types in vararg? (pair.first type depends on pair.second type)


回答1:


Instead of using Pair, consider defining your own type:

class WithSpec<P, T>(val prop: P?, val funCreateSpec: (P) -> Specification<T>) {
    fun spec() = prop?.let(funCreateSpec)
}

Why? Because it allows you to do

class GeneralSpecification<T> {
    fun ifNotNullCreateSpec(vararg propToCreateFun: WithSpec<*, T>): List<Specification<T>> = 
       propToCreateFun.mapNotNull { it.spec() }
    ...
}

ifNotNullCreateSpec(WithSpec("asdf", ::createStringSpec), WithSpec(5, ::createIntSpec))

You can easily add a to-like extension function returning WithSpec if you want to get even closer to your original code.

See https://kotlinlang.org/docs/reference/generics.html#star-projections if you don't know what * means.




回答2:


If you want to store the different functions together, you need to treat the parameter type T with out variance. This means T is only used in the output of the class. In practice, this means that Conversions of Spec<Derived> -> Spec<Base> are allowed, if Derived extends/implements Base.

Without such a constraint, the function types are not related, and as such you cannot store them in a common array (varargs are just syntactic sugar for array parameters).

Example:

class Spec<out T>

fun createStringSpec() = Spec<String>()
fun createIntSpec() = Spec<Int>()

fun <T> ifNotNullCreateSpec(vararg pairs: Pair<T, () -> Spec<T>>) = Unit

fun main() {
    ifNotNullCreateSpec("asdf" to ::createStringSpec, 5 to ::createIntSpec)
}

With a parameter T such as in (T) -> Spec<T>, the T type also appears in the input of the function type. This means that you can no longer store the function types together, because they take parameters of different types -- with which type would you invoke such a function?

What you would need to do here is to find the most common denominator. One example is to accept a Any parameter and do a runtime check/dispatch for the actual type.

See also my recent answer here: https://stackoverflow.com/a/55572849



来源:https://stackoverflow.com/questions/55692725/how-can-i-use-vararg-with-different-generics-in-kotlin

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!