Контракт Kotlin, который помогает компилятору Smartcast все элементы списка от обнуляемых до не обнуляемых? - PullRequest
1 голос
/ 07 ноября 2019

следующая ситуация: я пытаюсь реализовать универсальную функцию, которая проверяет, все ли списки переменных не равны NULL, и выполняет лямбду, для которой требуются переменные, не допускающие значения NULL.

Я могу объединить несколько вызовов letили реализовать несколько 'safeLet' -функций с 2,3,4 ... аргументами, но я все еще надеюсь, что возможна одна универсальная функция со списком.

Здесь текущий код с цепочками вызовов let:

val parameters = call.receiveParameters()
val firstName = parameters["firstName"]
val lastName = parameters["lastName"]

firstName?.let {
    lastName?.let { userService.add(UserDTO(firstName = firstName, lastName = lastName)) }
}

Вот моя текущая функция 'safeLet':

fun  <T> List<Any?>.safeLet(block: () -> T) {
    if(this.contains(null)) return

    block()
}

Но следующее по-прежнему не компилируется (поскольку параметры UserDTO являются String, а не String?):

listOf(firstName, lastName).safeLet {
    userService.add(UserDTO(firstName = firstName, lastName = lastName))
}

Я могу добавить! после firstName и lastName, чтобы избежать нулевой проверки, но это ужасно.

Моя идея - использовать контракты kotlin. Возможно ли что-то вроде этого:

@ExperimentalContracts
fun  <T> List<Any?>.safeLet(block: () -> T) {
    contract {
        returnsNotNull() implies {ALL ELEMENTS ARE NOT NULLABLE}
    }    

    if(this.contains(null)) return

    block()
}

Заранее спасибо.


По поводу комментария "filterNotNull" я сейчас попробовал это. Все еще не идеально, потому что я не люблю использовать здесь это [0] и это [1], но это работает:

allNotNull(firstName, lastName)?.apply {
    userService.add(UserDTO(firstName = this[0], lastName = this[1]))
}


fun <T : Any> allNotNull(vararg elements: T?): List<T>? = if(elements.contains(null)) null else elements.filterNotNull()

1 Ответ

0 голосов
/ 08 ноября 2019

Вы можете использовать функцию binding. Он принимает другую функцию, внутри которой вы можете использовать bind для преобразования нулевой ссылки в ненулевую.

Если вы передаете ненулевой аргумент bind, он возвращает его. В противном случае он приостанавливает выполнение блока binding.

Если выполнение приостановлено, binding возвращает null, в противном случае возвращается результат блока binding.


Вот как вы можете использовать binding:

binding { userService.add(UserDTO(firstName = firstName.bind(), lastName = lastName.bind())) }

Еще один пример:

fun sumOrNull(a: Int?, b: Int?): Int? = binding { a.bind() + b.bind() }

Вот моя реализация binding:

// startCoroutineUninterceptedOrReturn returns either COROUTINE_SUSPENDED or R
@Suppress("UNCHECKED_CAST")
fun <R> binding(block: suspend Binder.() -> R): R? =
    when (val result = block.startCoroutineUninterceptedOrReturn(Binder, BinderContinuation)) {
        COROUTINE_SUSPENDED -> null
        else -> result as R
    }

@RestrictsSuspension
object Binder {
    suspend fun <T> T?.bind(): T {
        if (this != null) return this
        suspendCoroutine<Nothing> {}
    }
}

suspend fun <T> Binder.bind(obj: T?): T {
    contract {
        returns() implies (obj != null)
    }
    return obj.bind()
}

private object BinderContinuation : Continuation<Any?> {
    override val context: CoroutineContext
        get() = EmptyCoroutineContext

    override fun resumeWith(result: Result<Any?>) {
        result.getOrThrow()
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...