In Kotlin, how can I test and use a value without computing it twice?

178 views Asked by At

Every so often, I find myself wanting to compute a value for some sort of filter operation, but then wanting to use that value when it's already disappeared into the condition-checking thing.

For instance:

val found = list.firstOrNull { slowConversion(it).isWanted() }
if (found != null) {
   something(found, slowConversion(found))
}

or

when {
   other_conditions -> other_actions
   list.any { it.contains(regex1) } -> something(list.firstOrNull { it.contains(regex1) } ?: "!!??")
}

For the slowConversion() I can work with a sequence mapped to pairs, although the terms first and second kinda confuse things a bit...

val pair = list.asSequence().map { it to slowConversion(it) }.firstOrNull { it.second.isWanted() }
if ( pair != null ) {
  something(pair.first, pair.second)
}

or if I only want the conversion,

val converted = list.firstNotNullOfOrNull { slowConversion(it).takeIf { it.isWanted() } }

but the best I can come up with to avoid the when duplication involves moving the action part into the condition part!

fun case(s: List<String>, r: Regex) {
   val match = s.firstOrNull { it.contains(r) }?.also { something(it) }
   return match != null
}

when {
  other_conditions -> other_actions
  case(list, regex1) -> true
}

At this point, it seems I should just have a stack of function calls linked together with ||

other_things || case(list, regex1) || case(list, regex2) || catchAll(list)

Is there something better or more concise for either of these?

1

There are 1 answers

1
Arpit Shukla On

You can write your first example like this:

for(element in list) {
    val result = slowConversion(element)
    if(result.isWanted()) {
        something(element, result)
        break
    }
}

This might not look very Kotlin-ish, but I think it's pretty straightforward & easy to understand.

For your second example, you can use the find function:

when {
    other_conditions -> other_actions
    else -> list.find { it.contains(regex1) }?.let(::something)
}

If you have multiple regexes, just iterate over them,

val regexes = listOf(regex1, regex2, ...)
for(regex in regexes) {
    val element = list.find { it.contains(regex1) } ?: continue
    something(element)
    break
}