【问题标题】:Why isn't a function tail recursive?为什么函数尾递归不是?
【发布时间】:2016-08-30 09:31:23
【问题描述】:

我正在阅读 M. Odersky 的《Scala 编程》,他这么说

类似近似的函数,它们称自己为最后一个 动作,称为尾递归。

所以,我尝试了这个:

object Main extends App {
    implicit val mc = new MyClass(8)
    val ti = new TestImplct
    ti.test
}

class TestImplct {
  def test(implicit mc : MyClass): Unit = {
    println(mc.i)
    mc.i -= 1
    if(mc.i < 0){
      throw new IllegalArgumentException
    }
    test
  }
}

class MyClass(var i : Int)

IDEONE DEMO

但它会生成以下堆栈跟踪

 Exception in thread "main" java.lang.IllegalArgumentException
    at TestImplct.test(Main.scala:13)
    at TestImplct.test(Main.scala:15)
    at TestImplct.test(Main.scala:15)
    at TestImplct.test(Main.scala:15)
    at TestImplct.test(Main.scala:15)
    at TestImplct.test(Main.scala:15)
    at TestImplct.test(Main.scala:15)
    at TestImplct.test(Main.scala:15)
    at TestImplct.test(Main.scala:15)

这意味着它为每个递归调用生成一个新的堆栈帧。但最后一个动作是调用它自己。出了什么问题以及如何使其尾递归?

为什么编译器不做尾调用优化?

【问题讨论】:

  • 范围内是否有 MyClass 的隐式实例?
  • @Samar 是的,我有。看演示
  • 我看到你已经定义了这个类。但我没有看到你在哪里实例化它。
  • @Samar 啊,你是对的。我没有附上完整的代码。我的道歉。
  • 它工作正常。为什么你认为它不是尾递归?它只是因为 if 条件而退出。

标签: scala recursion tail-recursion


【解决方案1】:

您可以尝试使用@tailrec 注释标记该方法。如果你这样做,编译将失败,并会告诉你为什么编译器不能将其优化为尾递归:

Main.scala:12: 错误:无法优化 @tailrec 带注释的方法测试:它既不是私有的也不是最终的,因此可以被覆盖

确实,如果您使用final 方法,它会按预期工作。

【讨论】:

  • 有趣。为什么在此处将方法设为 final 会允许 TCO?
  • @Samar + 1 也想知道。
  • 正如错误所说:否则方法可能会被某些不是尾递归的实现覆盖,那么您可能会认为您正在查看尾递归,而实际上并非所有调用都能够进行此优化。
【解决方案2】:

您的代码可以正常工作。您在mc 对象中的值每一步都会减少。在最后一步,您会遇到异常。

例如,您可以将函数的返回类型更改为Boolean,当您的值为&lt; 0 时返回false,否则返回true

我建议,您使用“@tailrec”注释来检查编译器的递归函数调用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-10
    • 2013-09-14
    • 2011-07-13
    相关资源
    最近更新 更多