Home > Back-end >  Skip chained function call on certain result(s)
Skip chained function call on certain result(s)

Time:09-13

I'm trying to implement a chain of function calls that could be expensive on their own, and I would like to only call the subsequent functions if the result of the previous satisfied the condition(s). For instance, I have these "models":

data class In(val a: Int, val c: String, val f: String, val t: String)

data class Out(val passed: Boolean, val code: String?)

...then this is the logic (never mind the variables/method names):

class Filter : SomeFilter {
  override fun filter(input: In): Out {
    return Stream.of(chML(input), chC(input), chA(input))
      .filter { !it.passed }
      .findFirst()
      .orElseGet { Out(true, "SUCCESS") }
  }

  private fun chC(input: In): Out {
    if (input.c !== "ADA") {
      return Out(false, "INVALID_C")
    }
    return Out(true, "SUCCESS")
  }

  private fun chA(input: In): Out {
    if (input.a >= 100) {
      return Out(false, "INVALID_A")
    }
    return Out(true, "SUCCESS")
  }

  private fun chML(input: In): Out {
    if (context.contains(input.f)) {
      // context.add(input.to)
      return Out(false, "INVALID_ML")
    }
    return Out(true, "SUCCESS")
  }
}

The problem with these functions is that they should be expensive, so if the output from any of those is Out.passed === false, then I wouldn't like to call the next one because it could be interpreted as a terminal operation at that point.

Is there a way to implement this in such way without going the if/else-if route? The approach with streams is cleaner, but it does execute all the functions, regardless.

CodePudding user response:

You can use sequence and yield

fun filter(input: In): Out {
    return sequence {
        yield(chML(input))
        yield(chC(input))
        yield(chA(input))
    }
        .firstOrNull { !it.passed }
        ?: Out(true, "SUCCESS")
}

CodePudding user response:

You can use function references and invoke them in a map operation. Kotlin Sequences short-circuit based on any filters in the chain, including firstOrNull.

override fun filter(input: In): Out {
    return sequenceOf(::chML, ::chC, ::chA)
        .map { it(input) }
        .firstOrNull { !it.passed }
        ?: Out(true, "SUCCESS")
}

By the way, I advise against using === or !== with Strings. That is checking reference equality which is very hard to use reliably unless all your source strings are private to this class so you can carefully make sure you know where/when they were instantiated and whether they were string literals. You should probably use != instead.

  • Related