【问题标题】:Isn't that code in tail recursive style?那不是尾递归风格的代码吗?
【发布时间】:2011-08-25 17:05:07
【问题描述】:

在阅读 David Pollack 的 Begging Scala 时,我对 Scala 有点陌生。 他定义了一个简单的递归函数,从文件中加载所有字符串:

def allStrings(expr: => String): List[String] = expr match {
    case null => Nil
    case w => w :: allStrings(expr)
}

它很优雅而且很棒,只是当我尝试加载一个巨大的字典文件时它抛出了 StackOverflow 异常。

现在据我了解 Scala 支持尾递归,因此函数调用不可能溢出堆栈,可能编译器无法识别它?因此,经过一番谷歌搜索后,我尝试使用 @tailrec 注释来帮助编译器,但它说

error: could not optimize @tailrec annotated method: it contains a recursive call not in tail position
def allStrings(expr: => String): List[String] =

我对尾递归的理解有误吗?如何修复此代码?

【问题讨论】:

    标签: scala functional-programming tail-recursion


    【解决方案1】:

    只有在最后一次调用是对方法本身的调用时,Scala 才能对此进行优化。

    嗯,最后一个调用不是allStrings,实际上是::(缺点)方法。

    使这个尾递归的一种方法是添加一个累加器参数,例如:

    def allStrings(expr: => String, acc: List[String] = Nil): List[String] =
      expr match {
        case null => acc
        case w => allStrings(expr, w :: acc)
      }
    

    为防止累加器泄漏到 API 中,可以将尾递归方法定义为嵌套方法:

    def allStrings(expr: => String) = {
      def iter(expr: => String, acc: List[String]): List[String] =
        expr match {
          case null => acc
          case w => iter(expr, w :: acc)
        }
      iter(expr, Nil)
    }
    

    【讨论】:

    • 复制我的答案时,您忘记了@tailrec 注释。这不仅是让编译器确认您的期望的简单方法,也是对后续维护者的有用提示。
    • 凯文,我没有复制你的答案,我实际上是在你发帖时进行编辑。但是你对注释提出了一个很好的观点,尽管你的讽刺评论我已经赞成你的回答:)
    • 看到我们同时回答时我打算删除我的,但在注意到您使用默认参数而不是嵌套方法后没有这样做。当后续编辑然后在您的答案中添加此替代方案时,您可以欣赏它看起来多么可疑。
    【解决方案2】:

    它不是尾递归(而且永远不可能),因为最终操作不是对allStrings 的递归调用,而是对:: 方法的调用。

    解决此问题的最安全方法是使用使用累加器的嵌套方法:

    def allStrings(expr: => String) = {
      @tailrec
      def inner(expr: => String, acc: List[String]): List[String] = expr match {
        case null => acc
        case w => inner(expr, w :: acc)
      }
      inner(expr, Nil)
    }
    

    在这种特殊情况下,您还可以将累加器提升到allStrings 上的参数,为其指定默认值Nil,并避免使用内部方法。但这并不总是可行的,如果您担心互操作,就无法从 Java 代码中很好地调用它。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-09-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-04-02
      相关资源
      最近更新 更多