为何我的递归调用并非尾调用?如何改写为尾递归?
尾递归警告问题解答
1. 警告出现的原因
- Kotlin的
tailrec修饰符要求递归调用必须是函数的最后一个执行操作,只有这样编译器才能把递归优化成循环,避免栈溢出。 - 你的代码里,所有
retry的递归调用都嵌套在fold函数的onFailureLambda回调内部,而且回调里还先执行了日志打印、delay(针对部分异常)等操作才调用retry。这种嵌套在Lambda里的递归不属于函数的直接尾位置,编译器无法识别为合法的尾递归,所以抛出警告。
2. 可以改写成尾递归函数
把原代码中runCatching+fold的逻辑换成try-catch结构,让递归调用直接成为函数的返回表达式,就能满足尾递归的要求。改写后的示例代码如下:
private tailrec suspend fun <T> retry(numberOfRetries: Int, block: suspend () -> T): Result<T> { return try { Timber.d("retry($numberOfRetries)") Result.success(block()) } catch (throwable: 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) } } } } }
- 这里用
try-catch替代了原来的链式调用,所有retry递归调用都是直接作为catch块分支的返回值,最终成为整个函数的返回表达式,属于合法的尾位置,编译器会认可这是尾递归,警告也就会消失。
内容的提问来源于stack exchange,提问作者david.mihola
相关产品推荐
相关产品推荐

