在 Kotlin 中仅向具有多个类型参数的扩展函数提供一个类型参数

gos*_*hki 5 generics default-constructor type-constraints kotlin extension-function

介绍

在 Kotlin 中,我有一个通用转换扩展函数,它可以简化从一种this类型的对象C到另一种类型的对象T(声明为receiver)的转换,并提供额外的转换action,该转换视为receiver原始this对象并提供对原始对象的访问:

inline fun <C, T, R> C.convertTo(receiver: T, action: T.(C) -> R) = receiver.apply {
    action(this@convertTo)
}
Run Code Online (Sandbox Code Playgroud)

它的使用方式如下:

val source: Source = Source()
val result = source.convertTo(Result()) {
    resultValue = it.sourceValue
    // and so on...
}
Run Code Online (Sandbox Code Playgroud)

我注意到我经常在由无参数构造函数创建的函数上使用这个函数,并且认为通过创建基于其类型自动构建receivers的附加版本来进一步简化它会很好,如下所示:convertTo()receiver

inline fun <reified T, C, R> C.convertTo(action: T.(C) -> R) = with(T::class.constructors.first().call()) {
    convertTo(this, action) // calling the first version of convertTo()
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,我不能这样称呼它:

source.convertTo<Result>() {}
Run Code Online (Sandbox Code Playgroud)

因为 Kotlin 期望提供三个类型参数。

问题

鉴于上述上下文,Kotlin 中是否可以创建一个具有多个类型参数的泛型函数,该函数只接受提供一个类型参数,而其他类型则由调用站点确定?

其他示例(由 @broot 提供)

想象一下 stdlib 中没有filterIsInstance(),而我们想实现它(或者我们是 stdlib 的开发者)。假设我们可以访问,@Exact因为这对于我们的示例很重要。最好将其声明为:

inline fun <C, T, R> C.convertTo(receiver: T, action: T.(C) -> R) = receiver.apply {
    action(this@convertTo)
}
Run Code Online (Sandbox Code Playgroud)

现在,像这样使用它是最方便的:

val source: Source = Source()
val result = source.convertTo(Result()) {
    resultValue = it.sourceValue
    // and so on...
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,我们必须使用一种解决方法:

inline fun <reified T, C, R> C.convertTo(action: T.(C) -> R) = with(T::class.constructors.first().call()) {
    convertTo(this, action) // calling the first version of convertTo()
}
Run Code Online (Sandbox Code Playgroud)

最后一张没那么糟糕。

现在,我们想创建一个函数来查找特定类型的项目并映射它们:

source.convertTo<Result>() {}
Run Code Online (Sandbox Code Playgroud)

同样,像这样使用它会很好:

inline fun <T, reified V : T> Iterable<@Exact T>.filterTyped(): List<V>
Run Code Online (Sandbox Code Playgroud)

相反,我们有这个:

val dogs = animals.filterTyped<Dog>() // compile error
Run Code Online (Sandbox Code Playgroud)

这仍然没有那么糟糕,但这个例子故意相对简单,以便于理解。实际上,该函数会更复杂,会有更多类型参数,lambda 会接收更多参数,等等,然后它就会变得很难使用。收到有关类型推断的错误后,用户必须彻底阅读函数的定义,以了解缺少什么以及在哪里提供显式类型。

顺便说一句:Kotlin 不允许这样的代码:cat is Dog,但允许这样的代码,这不是很奇怪吗cats.filterIsInstance<Dog>()?我们自己filterTyped()不允许这样做。所以也许(但只是也许),filterIsInstance()正是因为这个问题中描述的问题而被设计成这样的(它使用*而不是additional T)。

另一个例子,利用已有的reduce()功能。我们有这样的功能:

val dogs = animals.filterTyped<Animal, Dog>()
val dogs: List<Dog> = animals.filterTyped()
Run Code Online (Sandbox Code Playgroud)

(别问,问没有意义)

现在,减少狗的列表似乎非常简单:

inline fun <T, reified V : T, R> Iterable<T>.filterTypedAndMap(transform: (V) -> R): List<R>
Run Code Online (Sandbox Code Playgroud)

不幸的是,这是不可能的,因为编译器不知道如何正确S推断Animal. 我们不能轻易地S只提供,甚至提供返回类型也没有帮助:

animals.filterTypedAndMap<Dog> { it.barkingVolume } // compile error
Run Code Online (Sandbox Code Playgroud)

我们需要使用一些尴尬的解决方法:

animals.filterTypedAndMap<Animal, Dog, Int> { it.barkingVolume }
animals.filterTypedAndMap { dog: Dog -> dog.barkingVolume }
Run Code Online (Sandbox Code Playgroud)

Swe*_*per 2

类型参数R不是必需的:

inline fun <C, T> C.convertTo(receiver: T, action: T.(C) -> Unit) = receiver.apply {
    action(this@convertTo)
}
inline fun <reified T, C> C.convertTo(action: T.(C) -> Unit) = with(T::class.constructors.first().call()) {
    convertTo(this, action) // calling the first version of convertTo()
}
Run Code Online (Sandbox Code Playgroud)

如果您使用Unit,即使传入的函数具有非Unit返回类型,编译器仍然允许您传递该函数。

还有其他方法可以帮助编译器推断类型参数,而不仅仅是直接在<>. 您还可以注释变量的结果类型:

val result: Result = source.convertTo { ... }
Run Code Online (Sandbox Code Playgroud)

您还可以将名称更改convertTo为类似的名称convert,以使其更具可读性。

另一种选择是:

inline fun <T: Any, C> C.convertTo(resultType: KClass<T>, action: T.(C) -> Unit) = with(resultType.constructors.first().call()) {
    convertTo(this, action)
}

val result = source.convertTo(Result::class) { ... }
Run Code Online (Sandbox Code Playgroud)

然而,这会与第一次重载发生冲突。所以你必须以某种方式解决它。您可以重命名第一个重载,但我想不出任何好名字。我建议您像这样指定参数名称

source.convertTo(resultType = Result::class) { ... }
Run Code Online (Sandbox Code Playgroud)

旁注:我不确定无参数构造函数是否始终是构造函数列表中的第一个。我建议你实际上找到无参数构造函数。