Is there a way in kotlin to prevent function call if all (or some) arguments are null? For example Having function:
fun test(a: Int, b: Int) { /* function bo
If you try assigning null to any of the two Int
variables you will find that this doesn`t work. See the compile errors in the comments.
fun test(a: Int, b: Int) { /* function body here */ }
fun main(){
test(null, 0) //Null can not be value of non-null type Int
val b : Int? = null
test(0, b) // Type mismatch. Required: Int - Found: Int?
}
The example shows that in a pure Kotlin world test(a: Int, b: Int)
cannot be called with null
or even Int?
arguments. If you put Java in the mix I doubt there is a safe solution without null
checks, because you can call test(Int, Int)
with type Integer
from the Java side, which allows null. The Java "equivalent" to Int
would be @NotNull Integer
(which is not really null-safe).
NO! is the answer to your question(as far as I know)
Your best bet(assuming the function is not exposed) is what you said.
a?.let { b?.let { test(a, b) } }
If the function is exposed, and might be called without these checks, then you need to put the checks inside your function.
fun test(a: Int?, b: Int?) {
if (listOf(a, b).filterNotNull().size < 2) return
println("Function was called")
}
If you don't want callers to have to do these checks themselves, you could perform null checks in an intermediary function, and then call into the real implementation when they passed:
fun test(a: Int?, b: Int?) {
a ?: return
b ?: return
realTest(a, b)
}
private fun realTest(a: Int, b: Int) {
// use params
}
Edit: here's an implementation of the function @Alexey Romanov has proposed below:
inline fun <T1, T2, R> ifAllNonNull(p1: T1?, p2: T2?, function: (T1, T2) -> R): R? {
p1 ?: return null
p2 ?: return null
return function(p1, p2)
}
fun test(a: Int, b: Int) {
println("$a, $b")
}
val a: Int? = 10
val b: Int? = 5
ifAllNonNull(a, b, ::test)
Of course you'd need to implement the ifAllNonNull
function for 2, 3, etc parameters if you have other functions where you need its functionality.
You could define your own inline function for it.
inline fun <R, A> ifNotNull(a: A?, block: (A) -> R): R? =
if (a != null) {
block(a)
} else null
inline fun <R, A, B> ifNotNull(a: A?, b: B?, block: (A, B) -> R): R? =
if (a != null && b != null) {
block(a, b)
} else null
inline fun <R, A, B, C> ifNotNull(a: A?, b: B?, c: C?, block: (A, B, C) -> R): R? =
if (a != null && b != null && c != null) {
block(a, b, c)
} else null
inline fun <R, A, B, C, D> ifNotNull(a: A?, b: B?, c: C?, d: D?, block: (A, B, C, D) -> R): R? =
if (a != null && b != null && c != null && d != null) {
block(a, b, c, d)
} else null
And then you can call it like
ifNotNull(a, b, c, d) { a, b, c, d ->
...
}
I think the spirit of the OP was syntax, so my answer focuses on providing "let" for tuple types:
fun main() {
val p1: Int? = 10 // or null
val p2: Int? = 20 // or null
val p3: Int? = 30 // or null
val example1 = (p1 to p2).let(::testDouble)
val example2 = (p1 to p2).let { a, b -> a * b }
val example3 = (p1 to p2 to p3).let(::testTriple)
val example4 = (p1 to p2 to p3).let { a, b, c -> a * b * c }
}
fun testDouble(a: Int, b: Int): Int {
return a + b
}
fun testTriple(a: Int, b: Int, c: Int): Int {
return a + b + c
}
// Define let for Pair & Triple
fun <P1, P2, R> Pair<P1?, P2?>.let(f: (P1, P2) -> R): R? {
return f(first ?: return null, second ?: return null)
}
fun <P1, P2, P3, R> Triple<P1?, P2?, P3?>.let(f: (P1, P2, P3) -> R): R? {
return f(first ?: return null, second ?: return null, third ?: return null)
}
// Cute "to" syntax for Triple
infix fun <P1, P2, P3> Pair<P1?, P2?>.to(third: P3?): Triple<P1?, P2?, P3?> {
return Triple(first, second, third)
}
You can replace "to" with another word (see Triple extension as a guide), and you could extend to larger tuples (but Kotlin only provides 2 out-of-the-box & sadly I don't think it can be generic).