'How can I check if a set of functions all return non null, in a single expression?

Suppose I have three functions foo, bar, baz, all of which return nullable types.

fun foo(): Int? = 1
fun bar(): Int? = 2
fun baz(): Int? = 3

I want to call them, and if all them returns non-null, I want to compute a value from their return values.

I could do this with statements, like this:

val x = foo()
val y = bar()
val z = baz()
val result = if (x != null && y != null && z != null) x + y + z else null

However, I don't like the fact that I have to declare 3 extra variables that I can still access afterwards. By having 3 extra statements like this, it also means that I cannot use expression-bodied functions, if I were writing a function that returns result.

If I use lets instead:

val result = foo()?.let { x -> 
    bar()?.let { y -> 
        baz()?.let { z -> 
            x + y + z
        } 
    } 
}

This creates a deep nesting. If it were only one function, this would have been fine, but with 3 functions or more, this makes my intention of "call these three functions, if they are all non null, add them together" rather unclear.

How can I write this in a way that clearly conveys my intention, but also making it a single expression?



Solution 1:[1]

You could filter out all null-values and only apply an operation on the list, if it did not shrink in size, e.g.:

fun sumIfNoneNull(values: List<Int?>): Int? = values
    .filterNotNull()
    .takeIf { it.size == values.size }
    ?.sum()

One may generalize this further, e.g.:

fun <T, R> List<T>.foldIfNoneNull(
    initial: R,
    operation: (acc: R, T) -> R
): R? = this
    .filterNotNull()
    .takeIf { nonNullList -> nonNullList.size == this.size }
    ?.fold(initial, operation)

You may use this like any other fold, e.g.:

listOf(foo(), bar(), baz()).foldIfNoneNull(0) { acc, cur -> acc + cur }

Solution 2:[2]

val result = listOf(foo(), bar(), baz())
  .reduce { acc, i ->
    when {
      acc == null || i == null -> null
      else                     -> acc + i
    }
  }

Or as function:

fun <T> apply(operation: (T, T) -> T, vararg values: T?): T? {
  return values
    .reduce { acc, i ->
      when {
        acc == null || i == null -> null
        else                     -> operation(acc, i)
      }
    }
}

val result = apply({ x, y -> x + y }, foo(), bar(), baz())

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2