介绍
在 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 提供)
想象一下 stdlib 中没有filterIsInstance()
,我们想实现它(或者我们是 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 会接收更多参数等等,然后它就会变得难以使用。在收到关于类型推断的错误后,用户必须彻底阅读函数的定义以了解缺少什么以及在哪里提供显式类型。
cat is Dog
附带说明:Kotlin 不允许这样的代码: ,但允许这样的代码:这不是很奇怪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 }