Kotlin принудительно обнуляет универсальный тип в ненулевой тип того же универсального типа? - PullRequest
0 голосов
/ 18 января 2019

Прежде всего, я просто хочу отметить, что мне известно о Принудительное значение 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 как:

  1. Необнуляемый эквивалент типа T, если T обнуляем
  2. Быть таким же, как Tесли T является ненулевым типом

Возможно ли это в Kotlin?

Обновление:

Я создал ветку в проекте Awaitility под названиемhas-with-non-nullable-type где вы получаете ошибку времени компиляции, о которой я говорю в файле KotlinTest .Это то, что я хочу сделать компиляцией.Вы можете клонировать его, используя:

$ git clone https://github.com/awaitility/awaitility.git

Обновление 2:

Я добавил gist , что, как мне кажется, демонстрирует проблему без использования каких-либо зависимостей.

Ответы [ 2 ]

0 голосов
/ 20 января 2019

AwaitilityKtUntilFunCondition можно сделать контравариантным (поэтому AwaitilityKtUntilFunCondition<T> является подтипом AwaitilityKtUntilFunCondition<T?>) , и эта модификация вашей сущности, кажется, удовлетворяет требованиям:

// Fake Awaitility DSL
data class AwaitilityKtUntilFunCondition<out T>(val factory: ConditionFactory, val fn: () -> T)

infix fun <T : Any> AwaitilityKtUntilFunCondition<T?>.has(pred: T.() -> Boolean): T = factory.until(fn) { t: T? ->
    if (t == null) {
        false
    } else {
        pred(t)
    }
}!!

class ConditionFactory {
    fun <T : Any?> until(supplier: () -> T, predicate: (T) -> Boolean): T {
        val result = supplier()
        return if (predicate(result)) {
            result
        } else {
            throw IllegalArgumentException("Supplied value is not matching predicate")
        }
    }
}

class Await {
    infix fun <T> untilCallTo(supplier: () -> T): AwaitilityKtUntilFunCondition<T> {
        val conditionFactory = ConditionFactory()
        return AwaitilityKtUntilFunCondition(conditionFactory, supplier)
    }
}

// Example

data class Data(var state: String)
interface DataRepository<T> {
    fun loadData(): T
}

val nullableDataRepository: DataRepository<Data?> = TODO()
val nonNullableDataRepository: DataRepository<Data> = TODO()


// Both of these compile
val data1: Data = Await() untilCallTo { nonNullableDataRepository.loadData() } has {
    state == "something"
}

val data2: Data = Await() untilCallTo { nullableDataRepository.loadData() } has {
    state == "something"
}
0 голосов
/ 18 января 2019

Я создал минимальный пример, который достигает того, что вы хотите:

fun <T: Any> test(t: T?): T {
    // ...
    return t as T
}

Вы определяете верхнюю границу Any для T, поэтому она не может быть null. Для параметра t вы используете тип T?. В конце концов вы возвращаете t с кастом на T.

Примеры:

val a: String = test("Hello")
val b: String = test(null)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...