Kotlin - 为什么这个函数不符合尾递归的条件?

时间:2017-06-28 20:19:18

标签: kotlin tail-recursion

以下示例中的函数send()以递归方式调用自身:

internal inner class RouteSender(
        val features: List<Feature>,
        val exchange: GrpcUniExchange<Point, RouteSummary>
) {
    var result: AsyncResult<RouteSummary>? = null // Set in stub for recordRoute.

    fun send(numPoints: Int) {
        result?.let {
            // RPC completed or err'd before sending completed.
            // Sending further requests won't error, but they will be thrown away.
            return
        }

        val index = random.nextInt(features.size)
        val point = features[index].location
        println("Visiting point ${RouteGuideUtil.getLatitude(point)}, " +
                "${RouteGuideUtil.getLongitude(point)}")
        exchange.write(point)
        if (numPoints > 0) {
            vertx.setTimer(random.nextInt(1000) + 500L) { _ ->
                send(numPoints - 1)
            }
        } else {
            exchange.end()
        }
    }
}

可以重写它,以便执行的最后一个操作是对自身的递归调用:

...
if (numPoints <= 0) {
                exchange.end()
            } else {
                vertx.setTimer(random.nextInt(1000) + 500L) { _ ->
                    send(numPoints - 1)
                }
            }
...

然而,如果我将它标记为tailrec函数,我会收到一个警告:递归调用不是尾调用。这不会停止编译程序的成功运行。但是,为什么这不是尾巴召唤?

documentation说:

  

要获得tailrec修饰符的资格,函数必须调用自身   作为它执行的最后一个操作。你不能使用尾递归   递归调用后有更多代码,您无法使用它   在try / catch / finally块中。

这不在try / catch / finally块中,并且在递归调用之后没有更多代码。这是什么意味着这个代码块不适合尾递归优化?

我会回答我自己的问题,因为它没有回报价值。基于this讨论,这是我能想到的全部内容。想法?

2 个答案:

答案 0 :(得分:6)

虽然您的方法出现以包含对自身的调用,但实际上它根本不是递归方法。

send的调用显示在闭包内。这意味着它不会立即被调用。只有在调用闭包本身时才会调用它。在你的情况下,这是由计时器完成的。它将发生在当前调用堆栈之外,甚至可能在当前线程之外。

在任何情况下,最后一次通话都是对vertx.setTimer的调用。

答案 1 :(得分:0)

Kotlin允许内联闭包函数,它用于许多自己的库函数,例如forEach。如果从内联闭包中调用它,则从内联闭包返回后,tailrec可能会起作用。

但是,如上所述,这是一个定时器回调函数,因此根据定义它不能是内联调用,也不是尾递归。