Kotlin:相互递归函数的尾递归

时间:2016-03-01 04:28:55

标签: recursion kotlin tail-recursion

假设我写这样的代码:

tailrec fun odd(n: Int): Boolean =
        if (n == 0) false
        else even(n - 1)

tailrec fun even(n: Int): Boolean =
        if (n == 0) true
        else odd(n - 1)

fun main(args:Array<String>) {
    // :( java.lang.StackOverflowError
    System.out.println(even(99999))
}

如何让Kotlin优化这些相互递归的函数,以便我可以在不抛出StackOverflowError的情况下运行maintailrec关键字适用于单函数递归,但没有更复杂的。我还看到一个警告,即在使用tailrec关键字的地方找不到尾调用。也许这对编译器来说太难了?

3 个答案:

答案 0 :(得分:3)

维基百科https://en.wikipedia.org/wiki/Tail_call

  

尾部调用是作为过程的最终操作执行的子例程调用。如果尾调用可能导致稍后在调用链中再次调用相同的子例程,则子例程被称为尾递归

因此,根据定义,您的情况不是尾递归。那警告说的是什么。

目前,编译器无法对其进行优化,主要是因为这种情况非常罕见。 但我不确定即便是Haskel也会对此进行优化。

答案 1 :(得分:3)

您正在寻找的是#34;正确的尾部呼叫&#34;。 JVM不支持这些,因此您需要trampolines

正确的尾调用会在跳转(而不是调用)到尾部调用函数之前清理自己的函数(参数,局部变量)的内存。这样,被称为函数的尾部可以直接返回其调用者调用函数。无限的相互递归是可能的。 (在函数式语言中,这是最重要的特性之一。)

要在汇编程序中允许正确的尾调用,您需要一个命令来跳转(转到)通过指针引用的例程/方法。 OOP需要调用(存储位置跳回堆栈然后跳转)到通过指针引用的例程/方法。

您可以使用trampoline设计模式模拟正确的尾调用,也许通过库可以获得一些支持。 trampoline是一个while循环,它调用一个函数,该函数返回对下一个函数的引用,该函数返回对下一个函数的引用...

答案 2 :(得分:3)

以下是@ comonad&#39; s trampoline suggestion的实现。它有效!

import kotlin.reflect.KFunction

typealias Result = Pair<KFunction<*>?, Any?>
typealias Func = KFunction<Result>

tailrec fun trampoline(f: Func, arg: Any?): Any? {
    val (f2,arg2) = f.call(arg)
    @Suppress("UNCHECKED_CAST")
    return if (f2 == null) arg2 
        else trampoline(f2 as Func, arg2)
}

fun odd(n: Int): Result =
        if (n == 0) null to false
        else ::even to n-1

fun even(n: Int): Result =
        if (n == 0) null to true
        else ::odd to n-1

fun main(args:Array<String>) {
    System.out.println(trampoline(::even, 9999999))
}