how to break from lambda passed to recursive function when provided condition met

无人久伴 提交于 2019-12-13 03:33:06

问题


I am writing a custom loop dsl and I want it's usage to look like below


    var counter1 = 0
    var counter2 = 0
    loop {
            counter1 += 1
            println(counter1)
            stopIf(counter1 == 5) // loop should terminate here and not execute rest of the code if condition matches

            counter2 += 2
            println(counter2)
            stopIf(counter2 == 8) // loop should terminate here and not execute rest of the code if condition matches

        }

I have following code which does allows me to write stopIf any number of times and anywhere in the loop body but when condition matches it does not terminate immediately but executes rest of the loop body and then terminates.


    @UseExperimental(ExperimentalTime::class)
    open class Loop {
        var stop = false

        val loopInterval = 1.seconds

        suspend fun loop(block: suspend () -> Unit): Unit = loop(loopInterval, block)

        suspend fun loop(minimumInterval: Duration, block: suspend () -> Unit): Unit =
            loopWithoutDelay { delayedResult(maxOf(minimumInterval, loopInterval), block) }

        private suspend fun loopWithoutDelay(block: suspend () -> Unit) {
            block()
            if (stop) return else loopWithoutDelay(block)
        }

        suspend fun <T> delayedResult(minDelay: Duration, f: suspend () -> T): T = coroutineScope {
            val futureValue = async { f() }
            delay(minDelay.toJavaDuration())
            futureValue.await()
        }

        fun stopIf(condition: Boolean) {
            if (condition) {
                stop = condition // once stop condition matches, then do not override it with following false condtions
            }
        }
    }

    @ExperimentalTime
    suspend fun loop(block: suspend Loop.() -> Unit) =
        Loop().run { loop { block(this) } }

I have tried to use return with label but it did not work. Is there any way I can achieve this?


回答1:


It can be done for example with throwing a lightweight exception. You have to declare custom exception:

class LoopStopException : Throwable("Stop look", null, false, false) // lightweight throwable without the stack trace

and catch it in loopWithoutDelay:

private suspend fun loopWithoutDelay(block: suspend () -> Unit) {
    try {
        while (true) {
            block()
        }
    } catch (e: LoopStopException) {
        //do nothing
    }
}




回答2:


I didn't understand much about the function delayedResult, because none of the dsl's public functions return a result. However, I come up with an solution for cancelling the loop.

As far as I understood, we have to have a loop that doesn't block the current thread. Therefore, it must be run in a coroutine, but in order to be able to cancel the loop, the dsl must run its own coroutine. This inner coroutine is run using coroutineScope, so it suspends the parent coroutine until it's finished or cancelled.

@ExperimentalTime
class Loop {
    private val loopInterval = 1.seconds

    suspend fun loop(block: suspend () -> Unit) = loop(loopInterval, block)

    suspend fun loop(minimumInterval: Duration, block: suspend () -> Unit):Job  = coroutineScope {
        launch {
            while (true) {
                block()
                delay(minOf(minimumInterval, loopInterval).toLongMilliseconds())
            }
        }
    }

    suspend fun stopIf(condition: Boolean) = coroutineScope {
        suspendCancellableCoroutine<Unit> {
            if (condition) it.cancel() else it.resumeWith(Result.success(Unit))
        }
    }
}

@ExperimentalTime
suspend fun loop(block: suspend Loop.() -> Unit):Job {
    return Loop().run {
        this.loop {
            block(this)
        }
    }
}


来源:https://stackoverflow.com/questions/57927627/how-to-break-from-lambda-passed-to-recursive-function-when-provided-condition-me

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!