Kotlin多类型参数扩展函数能否仅指定单个显式类型参数?
引言
我在Kotlin中定义了一个泛型转换扩展函数,用于简化将类型为C的当前对象转换为另一个类型为T的对象(即声明的receiver)的流程,附带的转换action会将receiver作为当前上下文,同时支持访问原始对象:
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... }
我发现我经常在通过无参构造器创建receiver的场景下使用该函数,因此想进一步简化使用流程,新增一个版本的convertTo(),支持基于类型自动构造receiver,实现如下:
inline fun <reified T, C, R> C.convertTo(action: T.(C) -> R) = with(T::class.constructors.first().call()) { convertTo(this, action) // 调用第一个版本的convertTo() }
但我无法按照如下方式调用:
source.convertTo<Result>() {}
因为Kotlin要求传入全部三个类型参数。
问题
基于上述背景,Kotlin中能否定义带有多个类型参数的泛型函数,仅显式传入一个类型参数,其余类型由调用上下文自动推断?
补充示例(@broot 提供)
假设标准库中没有filterIsInstance(),我们需要自行实现该函数(或我们是标准库的开发人员)。假设我们可以使用@Exact注解,这对本例很重要。最优的定义方式应该是:
inline fun <T, reified V : T> Iterable<@Exact T>.filterTyped(): List<V>
最便捷的调用方式如下:
val dogs = animals.filterTyped<Dog>() // 编译报错
但我们只能选用以下其中一种变通方案:
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 } // 编译报错
但实际只能这么写:
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
(不用纠结逻辑合理性)
现在对Dog列表执行reduce操作看起来非常直接:
dogs.reduce { acc, item -> acc + item } // 编译报错
但实际上无法编译,因为编译器不知道如何将S正确推断为Animal。我们无法仅单独指定S,甚至提前指定返回类型也没用:
val animal: Animal = dogs.reduce { acc, item -> acc + item } // 编译报错
我们需要使用不太优雅的变通方案:
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 }
内容的提问来源于stack exchange,提问作者goshki

