xinaiz
xinaiz

Reputation: 7788

Kotlin call function only if all arguments are not null

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 body here */ }

I would like to prevent null checks in case when arguments are null. For example, for arguments:

val a: Int? = null
val b: Int? = null 

I would like to replace:

a?.let { b?.let { test(a, b) } }

with:

test(a, b)

I imagine that function definition syntax could look something like this:

fun test(@PreventNullCall a: Int, @PreventNullCall b: Int)

And that would be equivalent to:

fun test(a: Int?, b: Int?) {
    if(a == null) {
        return
    }

    if(b == null) {
        return
    }

    // function body here
}

Is something like that (or similar) possible to reduce caller (and possibly function author) redundant code?

Upvotes: 20

Views: 13583

Answers (6)

Dmitry Kaltovich
Dmitry Kaltovich

Reputation: 2270

According to KISS principle, the easiest way will be:

fun test(a: Int?, b: Int?) {
   if (listOfNotNull(a, b).size == 2) println("RUN")
}

Upvotes: 0

charles-allen
charles-allen

Reputation: 4081

Let for Tuples (Pair & Triple)

I think the spirit of the OP was syntax, so my answer focuses on providing "let" for tuple types:

Example use:

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
}

Extension Funs:

// 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).

Upvotes: 0

zsmb13
zsmb13

Reputation: 89548

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.

Upvotes: 7

Zachary Thomas
Zachary Thomas

Reputation: 37

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

Upvotes: 1

EpicPandaForce
EpicPandaForce

Reputation: 81539

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

Upvotes: 5

leonardkraemer
leonardkraemer

Reputation: 6783

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

Upvotes: 0

Related Questions