Wiele zmiennych wpuszcza Kotlin

127

Czy istnieje sposób na połączenie wielu let dla wielu zmiennych dopuszczających wartość null w kotlin?

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

Mam na myśli coś takiego:

fun example(first: String?, second: String?) {
    first?.let && second?.let { 
        // Do something just if both are != null
    }
}
Daniel Gomez Rico
źródło
1
Chcesz N przedmiotów, a nie tylko 2? Czy wszystkie elementy wymagają tego samego lub różnych typów? Czy wszystkie wartości należy przekazać do funkcji, jako listę czy jako pojedyncze parametry? Czy zwracana wartość powinna być pojedynczym elementem, czy grupą tej samej liczby elementów co dane wejściowe?
Jayson Minard,
Potrzebuję wszystkich argumentów, w tym przypadku może być dwóch, ale chciałem też wiedzieć, jak to zrobić, aby uzyskać więcej, w szybkim jest to takie łatwe.
Daniel Gomez Rico,
Szukasz czegoś innego niż odpowiedzi poniżej, jeśli tak, skomentuj, jakiej różnicy szukasz.
Jayson Minard,
Jak by to było odnieść się do pierwszego „tego” w drugim bloku let?
Javier Mendonça,

Odpowiedzi:

48

Jeśli jesteś zainteresowany, oto dwie z moich funkcji do rozwiązania tego problemu.

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())
    }
}

Stosowanie:


// 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)
}
Dario Pellegrini
źródło
To bardzo fajne, ale wciąż brakuje mi przypadku, w którym mogę użyć pierwszego wejścia w drugim. Przykład: ifLet ("A", toLower (first)) {// first = "A", second = "a"}
Otziii
Ponieważ w instrukcji ifLet pierwszy argument nie został jeszcze rozpakowany, funkcja taka jak twoja nie jest możliwa. Czy mogę zasugerować użycie guardLet? To całkiem proste. val (pierwsza) = guardLet (100) {return} val (second) = guardLet (101) {return} val średnia = średnia (pierwsza, druga) Wiem, że nie o to prosiłeś, ale mam nadzieję, że to pomoże.
Dario Pellegrini
Dzięki. Mam wiele sposobów rozwiązania tego problemu, powód jest taki, że w Swift można mieć wiele ifLets po sobie oddzielonych przecinkami i mogą używać zmiennych z poprzedniego sprawdzenia. Chciałbym, żeby to było możliwe również w Kotlinie. :)
Otziii
1
Można odebrać odpowiedź, ale każde połączenie jest obciążone. Ponieważ vm najpierw tworzy obiekt Function. Biorąc pod uwagę również ograniczenie dex, spowoduje to dodanie deklaracji klasy funkcji z 2 odwołaniami do metod dla każdego unikalnego sprawdzenia.
Oleksandr Albul
147

Oto kilka odmian, w zależności od tego, jakiego stylu będziesz chciał użyć, jeśli masz wszystko tego samego lub różnych typów i jeśli lista nieznana liczba elementów ...

Typy mieszane, wszystkie nie mogą mieć wartości null, aby obliczyć nową wartość

W przypadku typów mieszanych można utworzyć serię funkcji dla każdej liczby parametrów, które mogą wyglądać głupio, ale działają dobrze dla typów mieszanych:

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

Przykładowe użycie:

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

Wykonaj blok kodu, gdy lista nie zawiera elementów zerowych

Dwa warianty tutaj, po pierwsze, aby wykonać blok kodu, gdy lista zawiera wszystkie elementy niepuste, a po drugie, aby zrobić to samo, gdy lista zawiera co najmniej jeden element niezerowy. Oba przypadki przekazują listę elementów niezerowych do bloku kodu:

Funkcje:

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())
    }
}

Przykładowe użycie:

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"

Niewielka zmiana, aby funkcja otrzymywała listę elementów i wykonywała te same operacje:

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())
    }
}

Przykładowe użycie:

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

Te odmiany można zmienić, aby zwracały wartości, takie jak let() .

Użyj pierwszego niezerowego przedmiotu (Coalesce)

Podobnie jak w przypadku funkcji SQL Coalesce, zwraca pierwszy element inny niż pusty. Dwa smaki funkcji:

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

Przykładowe użycie:

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"

Inne odmiany

... Istnieją inne odmiany, ale przy większej specyfikacji można to zawęzić.

Jayson Minard
źródło
1
Można również łączyć whenAllNotNullz demontażu struktury tak: listOf(a, b, c).whenAllNotNull { (d, e, f) -> println("$d $e $f").
dumptruckman
10

Możesz w tym celu napisać własną funkcję:

 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
 }
yole
źródło
7

Możesz stworzyć arrayIfNoNullsfunkcję:

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

Możesz go następnie użyć dla zmiennej liczby wartości z let:

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

Jeśli masz już tablicę, możesz utworzyć takeIfNoNullsfunkcję (inspirowaną takeIfi requireNoNulls):

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

Przykład:

array?.takeIfNoNulls()?.let { (first, second) ->
    // Do something if each element is not null
}
mfulton26
źródło
3

W przypadku, gdy sprawdzasz tylko dwie wartości i nie musisz pracować z listami:

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

Przykład użycia:

var firstString: String?
var secondString: String?
ifNotNull(firstString, secondString) { first, second -> Log.d(TAG, "$first, $second") }
Jonas Hansson
źródło
2

Właściwie możesz to po prostu zrobić, wiesz? ;)

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

Nie ma nic złego w korzystaniu z normalnej kontroli zerowej w Kotlinie.

Jest znacznie bardziej czytelny dla każdego, kto zajrzy do Twojego kodu.

Grzegorz D.
źródło
36
To nie wystarczy, gdy mamy do czynienia ze zmiennym składnikiem klasy.
Michał K
3
Nie ma potrzeby udzielania tego rodzaju odpowiedzi, celem pytania jest znalezienie bardziej „produktywnego” sposobu rozwiązania tego problemu, ponieważ język zapewnia letskrót do wykonania tych kontroli
Alejandro Moya
1
Jeśli chodzi o konserwację, to mój wybór, nawet jeśli nie jest tak elegancki. Jest to oczywiście problem, z którym wszyscy się spotykają przez cały czas i język powinien sobie z tym poradzić.
Brill Pappin
2

Właściwie wolę rozwiązać ten problem za pomocą następujących funkcji pomocniczych:

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)

A oto jak ich używać:

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
  .
  .
  .
}
Moshe Bixenshpaner
źródło
1

Rozwiązałem to, tworząc funkcje, które w mniejszym lub większym stopniu replikują zachowanie with, ale przyjmują wiele parametrów i tylko wywołują funkcję, dla której wszystkie parametry nie są zerowe.

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) } } } } }

Następnie używam tego w ten sposób:

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")

Oczywistym problemem jest to, że muszę zdefiniować funkcję dla każdego przypadku (liczby zmiennych), których potrzebuję, ale przynajmniej myślę, że kod wygląda czysto, gdy ich używam.

Jon
źródło
1

Ty też możesz to zrobić

if (listOfNotNull(var1, var2, var3).size == 3) {
        // All variables are non-null
}
Amy Eubanks
źródło
Kompilator nadal będzie narzekał, że nie może zagwarantować, że vars nie są zerowe
Peter Graham
1

Zaktualizowałem nieco oczekiwaną odpowiedź:

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
}

to umożliwia:

iflet("first", "sconed") {
    // do somehing
} ?: run {
    // do this if one of the params are null
}
yohai knaani
źródło
To fajne, ale parametry nie są nazwane i powinny mieć taki sam typ.
Daniel Gomez Rico
0

Dla dowolnej ilości wartości do sprawdzenia możesz użyć tego:

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

I będzie używany w ten sposób:

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

    checkNulls(dada, dede) { strings ->

    }

elementy wysłane do bloku używają symboli wieloznacznych, musisz sprawdzić typy, jeśli chcesz uzyskać dostęp do wartości, jeśli potrzebujesz tylko jednego typu, możesz zmienić to na ogólne

Alejandro Moya
źródło