简介
在 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)
}
它是这样使用的:
val source: Source = Source()
val result = source.convertTo(Result()) {
resultValue = it.sourceValue
// and so on...
}
我注意到我经常在 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()
}
不幸的是,我不能这样调用它:
source.convertTo<Result>() {}
因为 Kotlin 需要提供三个类型参数。
问题
鉴于上述情况,是否有可能在 Kotlin 中创建一个具有多个类型参数的泛型函数,该函数接受仅提供一个类型参数而其他类型由调用站点确定?
其他示例(来自@broot)
假设没有 filterIsInstance()
在 stdlib 中,我们想实现它(或者我们是 stdlib 的开发人员)。假设我们可以访问 @Exact
因为这对我们的例子很重要。最好将其声明为:
inline fun <T, reified V : T> Iterable<@Exact T>.filterTyped(): List<V>
现在,像这样使用它会最方便:
val dogs = animals.filterTyped<Dog>() // compile error
不幸的是,我们必须使用其中一种解决方法:
val dogs = animals.filterTyped<Animal, Dog>()
val dogs: List<Dog> = animals.filterTyped()
最后一个还不错。
现在,我们要创建一个函数来查找特定类型的项目并映射它们:
inline fun <T, reified V : T, R> Iterable<T>.filterTypedAndMap(transform: (V) -> R): List<R>
同样,像这样使用它会很好:
animals.filterTypedAndMap<Dog> { it.barkingVolume } // compile error
相反,我们有这个:
animals.filterTypedAndMap<Animal, Dog, Int> { it.barkingVolume }
animals.filterTypedAndMap { dog: Dog -> dog.barkingVolume }
这还算不错,但是这个例子有意相对简单,以便于理解。实际上函数会更复杂,会有更多的类型参数,lambda 会接收更多的参数等等,然后它会变得难以使用。收到有关类型推断的错误后,用户必须通读函数的定义以了解缺少的内容以及在何处提供显式类型。
作为旁注:Kotlin 不允许这样的代码是不是很奇怪:cat is Dog
,但允许这样:cats.filterIsInstance<Dog>()
?我们自己的filterTyped()
不允许这样做。所以也许(但只是也许),filterIsInstance()
正是因为这个问题中描述的问题而设计成这样(它使用 *
而不是额外的 T
)。
另一个例子,利用已经存在的 reduce()
功能。我们有这样的功能:
operator fun Animal.plus(other: Animal): Animal
(别问,没意义)
现在,减少狗的列表似乎非常简单:
dogs.reduce { acc, item -> acc + item } // compile error
不幸的是,这是不可能的,因为编译器不知道如何正确推断 S
至 Animal
.我们不能轻易提供 S
仅提供甚至提供返回类型在这里都无济于事:
val animal: Animal = dogs.reduce { acc, item -> acc + item } // compile error
我们需要使用一些笨拙的解决方法:
dogs.reduce<Animal, Dog> { acc, item -> acc + item }
(dogs as List<Animal>).reduce { acc, item -> acc + item }
dogs.reduce { acc: Animal, item: Animal -> acc + item }
最佳答案
类型参数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()
}
如果您使用 Unit
,即使传入的函数有一个非 Unit
返回类型,编译器仍然允许您传递该函数。
还有其他方法可以帮助编译器推断类型参数,而不仅仅是直接在 <>
中指定它们.您还可以注释变量的结果类型:
val result: Result = source.convertTo { ... }
您还可以更改 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) { ... }
但是,这会与第一个重载冲突。所以你必须以某种方式解决它。您可以重命名第一个重载,但我想不出任何好名字。我建议您像这样指定参数名称
source.convertTo(resultType = Result::class) { ... }
旁注:我不确定无参数构造函数是否总是构造函数列表中的第一个。我建议您实际上找到无参数构造函数。
关于kotlin - 仅向 Kotlin 中具有多个类型参数的扩展函数提供一个类型参数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69583026/