Kotlin let is a scoping function wherein the variables declared inside the expression cannot be used outside. An example demonstrating kotlin let function is given below. it keyword contains the copy of the property inside let . The last value from the let is returned as an argument as shown below.
Variable number of arguments (varargs)Only one parameter can be marked as vararg . If a vararg parameter is not the last one in the list, values for the subsequent parameters can be passed using named argument syntax, or, if the parameter has a function type, by passing a lambda outside the parentheses.
To create a Pair object, call the class constructor Pair(first: A, second: B) . A and B in the constructor signature signify that you are not restricted to a particular type, and you can take advantage of Kotlin's generics features here, e.g. val rect = Pair<Int, Long>(90, 70000) .
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...
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
}
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()
.
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"
...There are other variations, but with more of a specification this could be narrowed down.
If interested here are two of my functions for solving this.
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())
}
}
Usage:
// 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)
}
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
}
I like the idea of using a list filtering the null values, I usually do something similar when I'm working with the same type, but when there are multiple types, to avoid the values parsed as Any
, I just do something like this
fun someFunction() {
val value1: String = this.value1 ?: return
val value2: Int = this.value2 ?: return
...
}
It works and for me is important keep the type safety
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
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With