Variable múltiple deja entrar Kotlin


127

¿Hay alguna forma de encadenar múltiples permisos para múltiples variables anulables en kotlin?

fun example(first: String?, second: String?) {
    first?.let {
        second?.let {
            // Do something just if both are != null
        }
    }
}

Quiero decir, algo como esto:

fun example(first: String?, second: String?) {
    first?.let && second?.let { 
        // Do something just if both are != null
    }
}

1
¿Quieres N artículos, no solo 2? ¿Todos los artículos necesitan el mismo tipo o diferentes tipos? ¿Se deben pasar todos los valores a la función, como lista o como parámetros individuales? ¿Debería el valor de retorno ser un solo artículo o un grupo del mismo número de artículos como entrada?
Jayson Minard

Necesito todos los argumentos, pueden ser dos para este caso, pero también quería saber una forma de hacer esto para obtener más información, en forma rápida es muy fácil.
Daniel Gomez Rico

¿Está buscando algo diferente a las respuestas a continuación? Si es así, comente cuál es la diferencia que está buscando.
Jayson Minard

¿Cómo sería referirse al primer "eso" dentro del segundo bloque let?
Javier Mendonça

Respuestas:


48

Si está interesado aquí hay dos de mis funciones para resolver esto.

inline fun <T: Any> guardLet(vararg elements: T?, closure: () -> Nothing): List<T> {
    return if (elements.all { it != null }) {
        elements.filterNotNull()
    } else {
        closure()
    }
}

inline fun <T: Any> ifLet(vararg elements: T?, closure: (List<T>) -> Unit) {
    if (elements.all { it != null }) {
        closure(elements.filterNotNull())
    }
}

Uso:


// Will print
val (first, second, third) = guardLet("Hello", 3, Thing("Hello")) { return }
println(first)
println(second)
println(third)

// Will return
val (first, second, third) = guardLet("Hello", null, Thing("Hello")) { return }
println(first)
println(second)
println(third)

// Will print
ifLet("Hello", "A", 9) {
 (first, second, third) ->
 println(first)
 println(second)
 println(third)
}

// Won't print
ifLet("Hello", 9, null) {
 (first, second, third) ->
 println(first)
 println(second)
 println(third)
}

Esto es muy bueno, pero todavía me falta un caso en el que pueda usar la primera entrada en la segunda. Ejemplo: ifLet ("A", toLower (first)) {// first = "A", second = "a"}
Otziii

Porque en la declaración ifLet el primer argumento aún no se ha desenvuelto, una función como su no es posible. ¿Puedo sugerir usar guardLet? Es bastante sencillo. val (first) = guardLet (100) {return} val (second) = guardLet (101) {return} val average = average (first, second) Sé que eso no es lo que pediste, pero espero que ayude.
Dario Pellegrini

Gracias. Tengo varias formas de resolver esto, la razón para decir es que en Swift es posible tener múltiples ifLets separados entre sí por comas y pueden usar las variables de la verificación anterior. Ojalá esto también fuera posible en Kotlin. :)
Otziii

1
Se podría aceptar la respuesta, pero hay una sobrecarga en cada llamada. Porque vm crea el objeto Function en primer lugar. También teniendo en cuenta la limitación de dex, esto agregará una declaración de clase de función con 2 referencias de método para cada verificación única.
Oleksandr Albul

146

Aquí hay algunas variaciones, según el estilo que desee usar, si tiene todo de tipos iguales o diferentes, y si la lista tiene un número desconocido de elementos ...

Tipos mixtos, todos no deben ser nulos para calcular un nuevo valor

Para los tipos mixtos, puede crear una serie de funciones para cada recuento de parámetros que pueden parecer tontas, pero funcionan bien para los tipos mixtos:

inline fun <T1: Any, T2: Any, R: Any> safeLet(p1: T1?, p2: T2?, block: (T1, T2)->R?): R? {
    return if (p1 != null && p2 != null) block(p1, p2) else null
}
inline fun <T1: Any, T2: Any, T3: Any, R: Any> safeLet(p1: T1?, p2: T2?, p3: T3?, block: (T1, T2, T3)->R?): R? {
    return if (p1 != null && p2 != null && p3 != null) block(p1, p2, p3) else null
}
inline fun <T1: Any, T2: Any, T3: Any, T4: Any, R: Any> safeLet(p1: T1?, p2: T2?, p3: T3?, p4: T4?, block: (T1, T2, T3, T4)->R?): R? {
    return if (p1 != null && p2 != null && p3 != null && p4 != null) block(p1, p2, p3, p4) else null
}
inline fun <T1: Any, T2: Any, T3: Any, T4: Any, T5: Any, R: Any> safeLet(p1: T1?, p2: T2?, p3: T3?, p4: T4?, p5: T5?, block: (T1, T2, T3, T4, T5)->R?): R? {
    return if (p1 != null && p2 != null && p3 != null && p4 != null && p5 != null) block(p1, p2, p3, p4, p5) else null
}
// ...keep going up to the parameter count you care about

Ejemplo de uso:

val risk = safeLet(person.name, person.age) { name, age ->
  // do something
}   

Ejecute bloque de código cuando la lista no tenga elementos nulos

Aquí hay dos opciones, la primera para ejecutar un bloque de código cuando una lista tiene todos los elementos no nulos, y la segunda para hacer lo mismo cuando una lista tiene al menos un elemento no nulo. Ambos casos pasan una lista de elementos no nulos al bloque de código:

Funciones:

fun <T: Any, R: Any> Collection<T?>.whenAllNotNull(block: (List<T>)->R) {
    if (this.all { it != null }) {
        block(this.filterNotNull()) // or do unsafe cast to non null collectino
    }
}

fun <T: Any, R: Any> Collection<T?>.whenAnyNotNull(block: (List<T>)->R) {
    if (this.any { it != null }) {
        block(this.filterNotNull())
    }
}

Ejemplo de uso:

listOf("something", "else", "matters").whenAllNotNull {
    println(it.joinToString(" "))
} // output "something else matters"

listOf("something", null, "matters").whenAllNotNull {
    println(it.joinToString(" "))
} // no output

listOf("something", null, "matters").whenAnyNotNull {
    println(it.joinToString(" "))
} // output "something matters"

Un ligero cambio para que la función reciba la lista de elementos y realice las mismas operaciones:

fun <T: Any, R: Any> whenAllNotNull(vararg options: T?, block: (List<T>)->R) {
    if (options.all { it != null }) {
        block(options.filterNotNull()) // or do unsafe cast to non null collection
    }
}

fun <T: Any, R: Any> whenAnyNotNull(vararg options: T?, block: (List<T>)->R) {
    if (options.any { it != null }) {
        block(options.filterNotNull())
    }
}

Ejemplo de uso:

whenAllNotNull("something", "else", "matters") {
    println(it.joinToString(" "))
} // output "something else matters"

Estas variaciones podrían ser cambiado a tener valores de retorno como let().

Utilice el primer elemento no nulo (fusión)

Similar a una función de fusión SQL, devuelve el primer elemento no nulo. Dos sabores de la función:

fun <T: Any> coalesce(vararg options: T?): T? = options.firstOrNull { it != null }
fun <T: Any> Collection<T?>.coalesce(): T? = this.firstOrNull { it != null }

Ejemplo de uso:

coalesce(null, "something", null, "matters")?.let {
    it.length
} // result is 9, length of "something"

listOf(null, "something", null, "matters").coalesce()?.let {
    it.length
}  // result is 9, length of "something"

Otras variaciones

... Hay otras variaciones, pero con una mayor especificación, esto podría reducirse.


1
También puede combinar whenAllNotNullcon la desestructuración de este modo: listOf(a, b, c).whenAllNotNull { (d, e, f) -> println("$d $e $f").
dumptruckman

10

Puede escribir su propia función para eso:

 fun <T, U, R> Pair<T?, U?>.biLet(body: (T, U) -> R): R? {
     val first = first
     val second = second
     if (first != null && second != null) {
         return body(first, second)
     }
     return null
 }

 (first to second).biLet { first, second -> 
      // body
 }

7

Puedes crear una arrayIfNoNullsfunción:

fun <T : Any> arrayIfNoNulls(vararg elements: T?): Array<T>? {
    if (null in elements) {
        return null
    }
    @Suppress("UNCHECKED_CAST")
    return elements as Array<T>
}

Luego puede usarlo para un número variable de valores con let:

fun example(first: String?, second: String?) {
    arrayIfNoNulls(first, second)?.let { (first, second) ->
        // Do something if each element is not null
    }
}

Si ya tiene una matriz, puede crear una takeIfNoNullsfunción (inspirada en takeIfy requireNoNulls):

fun <T : Any> Array<T?>.takeIfNoNulls(): Array<T>? {
    if (null in this) {
        return null
    }
    @Suppress("UNCHECKED_CAST")
    return this as Array<T>
}

Ejemplo:

array?.takeIfNoNulls()?.let { (first, second) ->
    // Do something if each element is not null
}

3

Para el caso de simplemente verificar dos valores y no tener que trabajar con listas:

fun <T1, T2> ifNotNull(value1: T1?, value2: T2?, bothNotNull: (T1, T2) -> (Unit)) {
    if (value1 != null && value2 != null) {
        bothNotNull(value1, value2)
    }
}

Ejemplo de uso:

var firstString: String?
var secondString: String?
ifNotNull(firstString, secondString) { first, second -> Log.d(TAG, "$first, $second") }

2

En realidad, simplemente puedes hacer esto, ¿sabes? ;)

if (first != null && second != null) {
    // your logic here...
}

No hay nada de malo en usar un cheque nulo normal en Kotlin.

Y es mucho más legible para todos los que analizarán su código.


36
No será suficiente cuando se trata de un miembro de clase mutable.
Michał K

3
No es necesario dar este tipo de respuesta, la intención de la pregunta es encontrar una forma más "productiva" de manejar esto, ya que el lenguaje proporciona el letatajo para hacer estas verificaciones
Alejandro Moya,

1
En términos de mantenibilidad, esta es mi elección, incluso si no es tan elegante. Este es claramente un problema con el que todo el mundo se encuentra todo el tiempo, y el lenguaje debe tratarlo.
Brill Pappin

2

De hecho, prefiero resolverlo usando las siguientes funciones auxiliares:

fun <A, B> T(tuple: Pair<A?, B?>): Pair<A, B>? =
    if(tuple.first == null || tuple.second == null) null
    else Pair(tuple.first!!, tuple.second!!)

fun <A, B, C> T(tuple: Triple<A?, B?, C?>): Triple<A, B, C>? =
    if(tuple.first == null || tuple.second == null || tuple.third == null) null
    else Triple(tuple.first!!, tuple.second!!, tuple.third!!)


fun <A, B> T(first: A?, second: B?): Pair<A, B>? =
    if(first == null || second == null) null
    else Pair(first, second)

fun <A, B, C> T(first: A?, second: B?, third: C?): Triple<A, B, C>? =
        if(first == null || second == null || third == null) null
        else Triple(first, second, third)

Y así es como debes usarlos:

val a: A? = someValue
val b: B? = someOtherValue
T(a, b)?.let { (a, b) ->
  // Shadowed a and b are of type a: A and b: B
  val c: C? = anotherValue
  T(a, b, c)
}?.let { (a, b, c) ->
  // Shadowed a, b and c are of type a: A, b: B and c: C
  .
  .
  .
}

1

Resolví esto creando algunas funciones que replican más o menos el comportamiento de with, pero toma múltiples parámetros y solo invoca la función de todos los parámetros que no es nula.

fun <R, A, B> withNoNulls(p1: A?, p2: B?, function: (p1: A, p2: B) -> R): R? = p1?.let { p2?.let { function.invoke(p1, p2) } }
fun <R, A, B, C> withNoNulls(p1: A?, p2: B?, p3: C?, function: (p1: A, p2: B, p3: C) -> R): R? = p1?.let { p2?.let { p3?.let { function.invoke(p1, p2, p3) } } }
fun <R, A, B, C, D> withNoNulls(p1: A?, p2: B?, p3: C?, p4: D?, function: (p1: A, p2: B, p3: C, p4: D) -> R): R? = p1?.let { p2?.let { p3?.let { p4?.let { function.invoke(p1, p2, p3, p4) } } } }
fun <R, A, B, C, D, E> withNoNulls(p1: A?, p2: B?, p3: C?, p4: D?, p5: E?, function: (p1: A, p2: B, p3: C, p4: D, p5: E) -> R): R? = p1?.let { p2?.let { p3?.let { p4?.let { p5?.let { function.invoke(p1, p2, p3, p4, p5) } } } } }

Entonces lo uso así:

withNoNulls("hello", "world", Throwable("error")) { p1, p2, p3 ->
    p3.printStackTrace()
    p1.plus(" ").plus(p2)
}?.let {
    Log.d("TAG", it)
} ?: throw Exception("One or more parameters was null")

El problema obvio con esto es que tengo que definir una función para cada caso (número de variables) que necesito, pero al menos creo que el código se ve limpio cuando los uso.


1

También podrías hacer esto

if (listOfNotNull(var1, var2, var3).size == 3) {
        // All variables are non-null
}

El compilador seguirá quejándose de que no puede garantizar que los vars no sean nulos
Peter Graham

1

He actualizado un poco la respuesta esperada:

inline fun <T: Any, R: Any> ifLet(vararg elements: T?, closure: (List<T>) -> R): R? {
    return if (elements.all { it != null }) {
        closure(elements.filterNotNull())
    } else null
}

Esto hace esto posible:

iflet("first", "sconed") {
    // do somehing
} ?: run {
    // do this if one of the params are null
}

Eso es genial, pero los parámetros no tienen nombre y deberían compartir el tipo.
Daniel Gómez Rico

0

Para cualquier cantidad de valores a comprobar, puede usar esto:

    fun checkNulls(vararg elements: Any?, block: (Array<*>) -> Unit) {
        elements.forEach { if (it == null) return }
        block(elements.requireNoNulls())
    }

Y se usará así:

    val dada: String? = null
    val dede = "1"

    checkNulls(dada, dede) { strings ->

    }

los elementos enviados al bloque están usando el comodín, debe verificar los tipos si desea acceder a los valores, si necesita usar solo un tipo, puede mutar esto a genéricos

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.