Supongamos que tengo tres funciones foo , bar , baz , todas las cuales devuelven tipos anulables.
fun foo(): Int? = 1 fun bar(): Int? = 2 fun baz(): Int? = 3Quiero llamarlos, y si todos devuelven un valor no nulo, quiero calcular un valor a partir de sus valores de retorno.
Podría hacer esto con declaraciones , como esta:
val x = foo() val y = bar() val z = baz() val result = if (x != null && y != null && z != null) x + y + z else null Sin embargo, no me gusta el hecho de que tengo que declarar 3 variables adicionales a las que aún puedo acceder después. Al tener 3 declaraciones adicionales como esta, también significa que no puedo usar funciones con cuerpo de expresión, si estuviera escribiendo una función que devuelve result .
Si uso let s en su lugar:
val result = foo()?.let { x -> bar()?.let { y -> baz()?.let { z -> x + y + z } } }Esto crea un anidamiento profundo. Si fuera solo una función, esto habría estado bien, pero con 3 funciones o más, esto hace que mi intención de "llamar a estas tres funciones, si no son nulas, sumarlas" no sea clara.
¿Cómo puedo escribir esto de una manera que transmita claramente mi intención, pero también convirtiéndolo en una sola expresión?
val result = listOf(foo(), bar(), baz()) .reduce { acc, i -> when { acc == null || i == null -> null else -> acc + i } }O como función:
fun <T> apply(operation: (T, T) -> T, vararg values: T?): T? { return values .reduce { acc, i -> when { acc == null || i == null -> null else -> operation(acc, i) } } } val result = apply({ x, y -> x + y }, foo(), bar(), baz())Puede filtrar todos los valores null y solo aplicar una operación en la lista, si no se redujo en tamaño, por ejemplo:
fun sumIfNoneNull(values: List<Int?>): Int? = values .filterNotNull() .takeIf { it.size == values.size } ?.sum()Uno puede generalizar esto aún más, por ejemplo:
fun <T, R> List<T>.foldIfNoneNull( initial: R, operation: (acc: R, T) -> R ): R? = this .filterNotNull() .takeIf { nonNullList -> nonNullList.size == this.size } ?.fold(initial, operation) Puede usar esto como cualquier otro fold , por ejemplo:
listOf(foo(), bar(), baz()).foldIfNoneNull(0) { acc, cur -> acc + cur }Si son de diferentes tipos, creo que necesita escribir sus propias funciones auxiliares como estas (se necesitan diferentes sobrecargas para diferentes números de parámetros, porque no hay otra forma de que el compilador conozca los tipos de argumentos):
inline fun <T : Any, U : Any, R> ifAllNotNull(t: T?, u: U?, block: (t: T, u: U) -> R): R? { return when { t != null && u != null -> block(t, u) else -> null } } inline fun <T : Any, U : Any, V : Any, R> ifAllNotNull(t: T?, u: U?, v: V?, block: (t: T, u: U, v: V) -> R): R? { return when { t != null && u != null && v != null -> block(t, u, v) else -> null } } val result = ifAllNotNull(foo(), bar(), baz()) { x, y, z -> x + y + z }Tenga en cuenta que los tres parámetros se evaluarán antes de comprobar si alguno es nulo.
O si desea hacer lo que describió (ocultar las tres variables después del cálculo del resultado) usando solo funciones de biblioteca estándar, puede usar run para limitar el alcance de las variables temporales:
val result = run { val x = foo() val y = bar() val z = baz() if (x != null && y != null && z != null) x + y + z else null }Esto también le daría la oportunidad de hacer un cortocircuito si lo desea:
val result = run { val x = foo() ?: return@run null val y = bar() ?: return@run null val z = baz() ?: return@run null x + y + z }