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

Recursion Kotlin:相互递归函数的尾部递归,recursion,kotlin,tail-recursion,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.StackOver

假设我编写如下代码:

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))
}
tailrec-fun-odd(n:Int):布尔值=
如果(n==0)为false
else偶数(n-1)
tailrec fun偶数(n:Int):布尔值=
如果(n==0)为真
else奇数(n-1)
趣味主线(args:Array){
//:(java.lang.StackOverflower错误
系统输出打印项次(偶数(99999))
}
我如何让Kotlin优化这些相互递归的函数,这样我就可以运行
main
,而不抛出StackOverflower错误?关键字
tailrec
适用于单函数递归,但没有比这更复杂的。我还看到一个警告,即在使用
tailrec
关键字的地方找不到尾部调用ps这对编译器来说太难了?

维基百科:

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

因此,根据定义,您的案例不是尾部递归。这不是警告所说的


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

您需要的是“正确的尾部调用”。JVM不支持这些,所以您需要

正确的尾部调用会在跳转(而不是调用)到尾部调用函数之前清除其自身函数(参数、局部变量)的内存。这样,尾部调用函数可以直接返回到其调用函数。可以实现无限交互递归。(在函数式语言中,这是最重要的特性之一。)

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

您可以使用trampoline设计模式模拟适当的尾部调用,可能通过库提供了一些支持。
蹦床是一个while循环,它调用一个函数,该函数返回对下一个函数的引用,该函数返回对下一个函数的引用…

这里是@comonad的一个实现。它可以工作

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))
}
import kotlin.reflect.k函数
typealias结果=对
typealias Func=k函数
tailrec趣味蹦床(f:Func,arg:Any?):Any{
val(f2,arg2)=f.调用(arg)
@抑制(“未选中的_CAST”)
如果(f2==null)arg2,则返回
else蹦床(f2为Func,arg2)
}
有趣的奇数(n:Int):结果=
如果(n==0)null为false
else::偶数到n-1
有趣的偶数(n:Int):结果=
如果(n==0)null为true
else::奇数到n-1
趣味主线(args:Array){
System.out.println(蹦床(::偶数,999999))
}

来自同一页(稍加编辑):“针对JVM的函数式语言[如Kotlin]倾向于]实现直接[或自]尾部递归,但不实现相互尾部递归。”我可以向你保证Haskell支持互尾递归。它支持互尾递归。真的吗?酷!我这么认为,因为它是Haskell。谢谢你的提示。你能进一步澄清吗?在偶数/奇数示例中,偶数和off的最后一个动作都是一个子程序调用,我们看到在调用链的后面调用了相同的子程序。因此定义这两个函数都是尾部递归的。相关的Wiki部分在这里:“适当的尾部调用”(如Haskell)不是优化;它们是允许Haskell运行时系统依赖的无限递归的必要保证。(假设第一个命令tail调用通过parameter给出的函数,这是第二个命令,其参数指向第三个。)函数的内联将是一种优化。您可以添加一个特性请求,以获得“相互尾部递归”特性,如果您想将它添加到Kotlin,这是最好的选择。如果已经请求或计划了,也可以先在那里搜索。我在这里创建了一个Kotlin问题:酷,听起来我们可以在JVM中通过编写一个蹦床方法来支持这一点,该方法使用给定参数调用方法引用。
偶数
奇数
函数ld被修改为返回方法引用和下一个参数。我们通过调用trampoline方法进行第一次调用,该方法引用了
even
函数和参数
99999