Multiple variable let in Kotlin

后端 未结 12 1140
失恋的感觉
失恋的感觉 2020-11-30 18:48

Is there any way to chain multiple lets for multiple nullable variables in kotlin?

fun example(first: String?, second: String?) {
    first?.let {
        se         


        
相关标签:
12条回答
  • 2020-11-30 19:25

    I solved this by creating some functions that more or less replicates the behavior of with, but takes multiple parameters and only invokes the function of all the parameters is non-null.

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

    Then I use it like this:

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

    The obvious issue with this is that I have to define a function for each case (number of variables) I need, but at least I think the code looks clean when using them.

    0 讨论(0)
  • 2020-11-30 19:28

    You can create an arrayIfNoNulls function:

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

    You can then use it for a variable number of values with let:

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

    If you already have an array you can create a takeIfNoNulls function (inspired by takeIf and requireNoNulls):

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

    Example:

    array?.takeIfNoNulls()?.let { (first, second) ->
        // Do something if each element is not null
    }
    
    0 讨论(0)
  • 2020-11-30 19:31

    I actually prefer to solve it using the following helper functions:

    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)
    

    And here's how you should use them:

    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
      .
      .
      .
    }
    
    0 讨论(0)
  • 2020-11-30 19:34

    You could also do this

    if (listOfNotNull(var1, var2, var3).size == 3) {
            // All variables are non-null
    }
    
    0 讨论(0)
  • 2020-11-30 19:37

    You can write your own function for that:

     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
     }
    
    0 讨论(0)
  • 2020-11-30 19:39

    Here are a few variations, depending on what style you will want to use, if you have everything of same or different types, and if the list unknown number of items...

    Mixed types, all must not be null to calculate a new value

    For mixed types you could build a series of functions for each parameter count that may look silly, but work nicely for mixed types:

    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
    

    Example usage:

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

    Execute block of code when list has no null items

    Two flavours here, first to execute block of code when a list has all non null items, and second to do the same when a list has at least one not null item. Both cases pass a list of non null items to the block of code:

    Functions:

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

    Example usage:

    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"
    

    A slight change to have the function receive the list of items and do the same operations:

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

    Example usage:

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

    These variations could be changed to have return values like let().

    Use the first non-null item (Coalesce)

    Similar to a SQL Coalesce function, return the first non null item. Two flavours of the function:

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

    Example usage:

    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"
    

    Other variations

    ...There are other variations, but with more of a specification this could be narrowed down.

    0 讨论(0)
提交回复
热议问题