Прежде всего, я просто хочу отметить, что мне известно о Принудительное значение NULL в ненулевые типы и Универсальные Kotlin и обнуляемые типы классов но я неЯ не думаю, что эти вопросы совпадают с моими (поправьте меня, если я ошибаюсь).
Справочная информация
Я занимаюсь разработкой библиотеки под названием Awaitility который, проще говоря, предназначен для ожидания, пока предикат не станет истинным. Kotlin API предоставляет способ записи выражений, подобных этому:
// Create a simple data class example
data class Data(var value: String)
// A fake repository that returns a possibly nullable instance of Data
interface DataRepository {
// Invoked from other thread
fun loadData() : Data?
}
val dataRepository = .. // Implementation of DataRepository
// Now Awaitility allows you to wait until the "value" in Data is equal to "Something"
val data : Data = await untilCallTo { dataRepository.loadData() } has {
value == "Something"
}
Это работает, потому что has
возвращает false
, если dataRepository.loadData()
возвращает null
и никогда не вызывает предоставленныйфункция приемника ({ value == "Something" }
), если data
равно null
.Awaitility также выдаст исключение, если условие не выполнено, поэтому мы знаем , что то, что возвращается из выражения, имеет тип Data
(а не Data?
), как вы можете видеть в примере.
Функция has
реализована следующим образом:
infix fun <T> AwaitilityKtUntilFunCondition<T?>.has(pred: T.() -> Boolean) = factory.until(fn) { t: T? ->
if (t == null) {
false
} else {
pred(t)
}
} as T
, где AwaitilityKtUntilFunCondition
выглядит следующим образом:
data class AwaitilityKtUntilFunCondition<T> internal constructor(internal val factory: ConditionFactory, internal val fn: () -> T?)
(вы также можете найти ConditionFactory здесь при необходимости)
Хотя приведенный выше пример отлично работает, когда лямбда, переданная в untilCallTo
, возвращает обнуляемый тип (Data?
), она не компилируется, если мы передаем ей ненулевой тип (то есть Data
).Например, если мы просто изменим хранилище так, чтобы оно выглядело так:
interface DataRepository {
// Invoked from other thread
fun loadData() : Data // Notice that loadData now returns a non-nullable type
}
, и если мы тогда попробуем то же выражение Awaitility, что и в предыдущем примере:
val data : Data = await untilCallTo { dataRepository.loadData() } has {
value == "Something"
}
, мы получимошибка времени компиляции:
Error:(160, 20) Kotlin: Type mismatch: inferred type is AwaitilityKtUntilFunCondition<Data> but AwaitilityKtUntilFunCondition<Data?> was expected
Error:(160, 68) Kotlin: Type inference failed. Please try to specify type arguments explicitly.
Что (конечно) правильно!
Вопрос
Что я хочу сделать, так это как-тоизмените метод has
, чтобы тип возвращаемого значения всегда был ненулевым эквивалентом типа, передаваемого в качестве аргумента (который может быть как обнуляемым, так и ненулевым).Я пытался сделать что-то вроде этого (что не работает):
infix fun <T, T2> AwaitilityKtUntilFunCondition<T>.has(pred: T2.() -> Boolean): T2
where T : Any?, // Any? is not required but added for clarity
T2 : T!! // This doesn't compile
= factory.until(fn) { t: T ->
if (t == null) {
false
} else {
pred(t as T2)
}
} as T2
Это не компилируется из-за T2 : T!!
, но я надеюсь, что это показывает мое намерение.Т.е. я хочу как-то определить T2
как:
- Необнуляемый эквивалент типа
T
, если T
обнуляем - Быть таким же, как
T
если T
является ненулевым типом
Возможно ли это в Kotlin?
Обновление:
Я создал ветку в проекте Awaitility под названиемhas-with-non-nullable-type
где вы получаете ошибку времени компиляции, о которой я говорю в файле KotlinTest .Это то, что я хочу сделать компиляцией.Вы можете клонировать его, используя:
$ git clone https://github.com/awaitility/awaitility.git
Обновление 2:
Я добавил gist , что, как мне кажется, демонстрирует проблему без использования каких-либо зависимостей.