介紹
在科特林我有一個通用的轉換擴展函式的簡化轉換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 }
This is still not that bad, but the example is intentionally relatively simple to make it easy to understand. In reality the function would be more complicated, would have more typed params, lambda would receive more arguments, etc. and then it would become hard to use. After receiving the error about type inference, the user would have to read the definition of the function thoroughly to understand, what is missing and where to provide explicit types.
As a side note: isn't it strange that Kotlin disallows code like this: cat is Dog, but allows this: cats.filterIsInstance<Dog>()? Our own filterTyped() would not allow this. So maybe (but just maybe), filterIsInstance() was designed like this exactly because of the problem described in this question (it uses * instead of additional T).
Another example, utilizing already existing reduce() function. We have function like this:
operator fun Animal.plus(other: Animal): Animal
(Don't ask, it doesn't make sense)
Now, reducing a list of dogs seems pretty straightforward:
dogs.reduce { acc, item -> acc item } // compile error
Unfortunately, this is not possible, because compiler does not know how to properly infer S to Animal. We can't easily provide S only and even providing the return type does not help here:
val animal: Animal = dogs.reduce { acc, item -> acc item } // compile error
We need to use some awkward workarounds:
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 }
uj5u.com熱心網友回復:
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) { ... }
旁注:我不確定無引數建構式是否始終是建構式串列中的第一個。我建議您實際上找到無引數建構式。
uj5u.com熱心網友回復:
這個答案并沒有解決所述的問題,但結合了@Sweeper 的輸入以提供一種至少簡化結果物件實體化的解決方法。
首先,如果我們明確說明變數的結果型別(即val result: Result = source.convertTo {}),主要陳述的問題可以得到一定程度的緩解,但在@broot 描述的情況下還不足以解決問題。
其次,使用KClass<T>作為結果引數型別提供了KClass<T>.createInstance()確保我們找到一個無引數建構式的能力(如果有的話——如果沒有,那么結果實體化convertTo()就沒有資格使用)。我們還可以從 Kotlin 的默認引數值中受益,使結果引數型別可以從呼叫中省略,我們只需要考慮action可能作為 lambda(呼叫的最后一個引數)或函式參考提供的引數——這將需要兩個版本的結果實體化convertTo().
因此,考慮到以上所有內容,我想出了以下實作convertTo():
// version A: basic, expects explicitly provided instance of `receiver`
inline fun <C, T> C.convertTo(receiver: T, action: T.(C) -> Unit) = receiver.apply {
action(this@convertTo)
}
// version B: can instantiate result of type `T`, supports calls where `action` is a last lambda
inline fun <C, reified T : Any> C.convertTo(resultType: KClass<T> = T::class, action: T.(C) -> Unit) = with(resultType.createInstance()) {
(this@convertTo).convertTo(this@with, action)
}
// version C: can instantiate result of type `T`, supports calls where `action` is passed by reference
inline fun <C, reified T : Any> C.convertTo(action: T.(C) -> Unit, resultType: KClass<T> = T::class) = with(resultType.createInstance()) {
(this@convertTo).convertTo(T::class, action)
}
所有三個版本都可以根據特定用例協同作業。下面是一組示例,說明在什么情況下使用什么版本。
class Source { var sourceId = "" }
class Result { var resultId = "" }
val source = Source()
fun convertX(result: Result, source: Source) {
result.resultId = source.sourceId
}
fun convertY(result: Result, source: Source) = true
fun Source.toResultX(): Result = convertTo { resultId = it.sourceId }
fun Source.toResultY(): Result = convertTo(::convertX)
val result0 = source.convertTo(Result()) { resultId = it.sourceId } // uses version A of convertTo()
val result1: Result = source.convertTo { resultId = it.sourceId } // uses version B of convertTo()
val result2: Result = source.convertTo(::convertX) // uses version C of convertTo()
val result3: Result = source.convertTo(::convertY) // uses version C of convertTo()
val result4: Result = source.toResultX() // uses version B of convertTo()
val result5: Result = source.toResultY() // uses version C of convertTo()
PS:正如@Sweeper 所注意到的,convertTo結果實體化版本可能不是一個好名字(因為它不像基本版本那樣可讀),但這是次要問題。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/323569.html
標籤:kotlin generics default-constructor type-constraints extension-function
