我的递归调用怎么不是尾调用?

问题描述 投票:0回答:1

在以下代码中,所有对

retry
的调用都会收到警告“递归调用不是尾部调用”:

private tailrec suspend fun <T> retry(numberOfRetries: Int, block: suspend () -> T): Result<T> =
    runCatching {
        Timber.d("retry($numberOfRetries)")
        block()
    }.fold(
        onSuccess = { Result.success(it) },
        onFailure = { throwable ->
            when (throwable) {
                is TimeoutCancellationException -> {
                    Timber.e(throwable, "Request ran into timeout - retrying immediately...")
                    if (numberOfRetries > 0) {
                        retry(numberOfRetries - 1, block)
                    } else {
                        Result.failure(throwable)
                    }
                }

                is HttpException -> {
                    if (throwable.code() == HttpStatusCode.NOT_FOUND_404) {
                        Timber.e(throwable, "Request returned 404 - don't retry!")
                        Result.failure(throwable)
                    } else {
                        Timber.e(throwable, "Request returned some other error code - retrying in 3 seconds...")
                        if (numberOfRetries > 0) {
                            delay(DELAY_BEFORE_RETRY_MS)
                            retry(numberOfRetries - 1, block)
                        } else {
                            Result.failure(throwable)
                        }
                    }
                }

                else -> {
                    Timber.e(throwable, "Some other problem with request - retrying in 3 seconds...")
                    if (numberOfRetries > 0) {
                        delay(DELAY_BEFORE_RETRY_MS)
                        retry(numberOfRetries - 1, block)
                    } else {
                        Result.failure(throwable)
                    }
                }
            }
        }
    )
  1. 为什么?
  2. 有没有办法重写这个函数,使其成为尾递归?
android kotlin recursion tail-recursion
1个回答
0
投票
  1. 为什么?

看起来 tailrec 检测没有通过内联高阶函数。您在这里使用

fold
并传递回调,您不是直接从
retry
的主体调用
retry

这是一个演示该问题的更简单的示例:

private tailrec fun doThing(count: Int) {
    inlineLambdaWrap {
        if (count > 0) {
            doThing(count - 1) // Warning: Recursive call is not a tail call
        } else {
            println("STOP")
        }
    }
}

private inline fun inlineLambdaWrap(block: () -> Unit) {
    block()
}
  1. 有没有办法重写这个函数,使其成为尾递归?

我认为

try
/
catch
/
finally
块中也不支持尾递归,因此摆脱
runCatching
/
fold
在这个意义上没有帮助。

话虽这么说,重试本质上是一种重复,所以我一开始就不会采用递归方法。例如,使用递归使得我们不清楚最终会报告哪个错误。它还在您当前的代码中添加了一堆额外的

if
。这个怎么样?


private suspend fun <T> retryRequest(numberOfRetries: Int, block: suspend () -> T): Result<T> {
    lateinit var lastError: Throwable
    repeat(numberOfRetries) { attempt ->
        try {
            Timber.d("retry(${numberOfRetries - attempt})")
            return Result.success(block())
        } catch (e: TimeoutCancellationException) {
            Timber.e(e, "Request ran into timeout - retrying immediately...")
            lastError = e
        } catch (e: HttpException) {
            if (e.code() == HttpStatusCode.NOT_FOUND_404) {
                Timber.e(e, "Request returned 404 - don't retry!")
                return Result.failure(e)
            } else {
                Timber.e(e, "Request returned some other error code - retrying in 3 seconds...")
                delay(DELAY_BEFORE_RETRY_MS)
                lastError = e
            }
        } catch (t: Throwable) {
            Timber.e(t, "Some other problem with request - retrying in 3 seconds...")
            delay(DELAY_BEFORE_RETRY_MS)
            lastError = t
        }
    }
    return Result.failure(lastError)
}

我什至会说不要在业务代码中使用

Result
,然后扔掉,但这会影响你的签名,所以我不会更改它。

© www.soinside.com 2019 - 2024. All rights reserved.