【问题标题】:Kotlin: Tail recursion for mutually recursive functionsKotlin:相互递归函数的尾递归
【发布时间】:2016-06-13 09:14:51
【问题描述】:

假设我这样写代码:

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 优化这些相互递归的函数,以便我可以运行 main 而不会引发 StackOverflowError? tailrec 关键字适用于单函数递归,但没有更复杂的。我还看到一条警告,在使用 tailrec 关键字的地方找不到尾调用。也许这对编译器来说太难了?

【问题讨论】:

  • 您可以向youtrack.jetbrains.com 添加功能请求以获取“相互尾递归”功能,如果您希望将其添加到 Kotlin,这是最好的选择。如果已经请求或计划了,也请先在那里搜索。
  • 我在这里创建了一个 Kotlin 问题:youtrack.jetbrains.com/issue/KT-11307

标签: recursion kotlin tail-recursion


【解决方案1】:

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

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

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

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

【讨论】:

  • 来自同一页面(稍作编辑):“针对 JVM 的函数式语言 [如 Kotlin] [倾向于] 实现直接 [或自身] 尾递归,而不是相互尾递归。”我可以向你保证,Haskell 支持相互尾递归。
  • 有吗?凉爽的!我是这么认为的,只是因为它是 Haskell,它会。感谢您的提示。
  • 您能进一步澄清一下吗?在偶数/奇数示例中,偶数和关闭的最终动作都是子程序调用,我们看到在调用链中稍后会调用同一个子程序。因此,根据定义,这两个函数都是尾递归的。
  • “正确的尾调用”(就像在 Haskell 中一样)不是优化;它们是允许 Haskell 运行时系统所依赖的无限递归的必要保证。 (想象第一个命令尾部调用一个通过参数给出的函数,这是第二个命令,其参数指向第三个。)函数的内联将是一种优化。
【解决方案2】:

您正在寻找的是“正确的尾调用”。 JVM 不支持这些,所以你需要trampolines

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

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

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

【讨论】:

  • 酷,听起来我们可以通过编写一个蹦床方法在 JVM 中支持这一点,该方法使用给定的参数调用方法引用。 evenodd 函数应修改为返回方法引用和下一个参数。我们通过引用 even 函数和参数 99999 调用 trampoline 方法来进行第一次调用。
【解决方案3】:

这是@comonad 的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))
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-09-14
    • 1970-01-01
    • 2016-01-06
    • 2020-09-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-21
    相关资源
    最近更新 更多