18

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?

xinaiz
  • 7,744
  • 6
  • 34
  • 78
  • Try creating a function that accepts [varargs](https://www.packtpub.com/mapt/book/application_development/9781787126367/4/ch04lvl1sec59/varargs) and check null inside loop. – Enzokie Jun 07 '18 at 13:16
  • 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. The Java "equivalent" to `Int` would be `@NotNull Integer` (which is not really null-safe). – leonardkraemer Jun 07 '18 at 13:18

6 Answers6

6

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.

EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
zsmb13
  • 85,752
  • 11
  • 221
  • 226
  • That seems like best pure-kotlin solution, but i'd rather not have function signature that indicates that arguments are optional. I wanted to make that transparent to the caller. – xinaiz Jun 07 '18 at 13:38
  • Unfortunately, there's no way for the caller to believe that the arguments are non-nullable but to be able to safely pass nullable arguments to the function. You'll either have to perform null checks at the call site or inside the function. – zsmb13 Jun 07 '18 at 13:58
  • 2
    Allowing this would not fit with Kotlin's approach to null-safety. You could generalize the approach to not require an extra function for each `test`, and get e.g. `ifAllNonNull(a, b, ::test)`. – Alexey Romanov Jun 07 '18 at 14:04
  • I actually wrote that function just earlier today for a similiar problem, I've edited my answer with its implementation :) – zsmb13 Jun 07 '18 at 14:12
  • I was thinking along the lines of `ifAllNull`, but i didn't like it was arity-specific. I didn't see a way to avoid it. – Marko Topolnik Jun 07 '18 at 14:13
4

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 ->
    ...
}
EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
1

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

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

leonardkraemer
  • 6,573
  • 1
  • 31
  • 54
0

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

charles-allen
  • 3,891
  • 2
  • 23
  • 35
0

According to KISS principle, the easiest way will be:

fun test(a: Int?, b: Int?) {
   if (listOfNotNull(a, b).size == 2) println("RUN")
}
Dmitry Kaltovich
  • 2,046
  • 19
  • 21