【问题标题】:Tail recursion between two functions两个函数之间的尾递归
【发布时间】:2016-02-01 00:50:26
【问题描述】:

我想对 initConnection 进行初始调用,它调用 getData 递归调用自身,直到它需要刷新连接 ID,然后它调用 initConnection。

@tailrec private def initConnection(): Unit =
  {
    val response: Future[Response] = initHeaders(WS.url(url)).post(auth)
    response.onSuccess {
      case resp => getData(20, resp.asInstanceOf[Response].header("connectionID").get)
    }
    response.onFailure {
      case resp => initConnection()
    }
  }

@tailrec private def getData(requestsLeft: Int, sessionId: String): Unit =
  {
    if (requestsLeft == 0)
      {
        initConnection()
      }
    else
      {
        //send request and process data
        getData(requestsLeft - 1, sessionId)
      }
  }

我在 IntelliJ 中收到“递归调用不在尾部位置”错误,仅针对 initConnection 函数。不能在两个函数之间使用尾递归吗?还是仅与我的 Future[Response] 有关?

我也尝试删除 Future[Response]

@tailrec private def initConnection(): Unit =
  {
    val response: Response = initHeaders(WS.url(url)).post(auth).value.get.get
    getData(20, response.header("ConnectionID").get)
  }

并得到一个关于 initConnection 的错误,其中不包含递归调用。然而这显然是无限递归的

【问题讨论】:

    标签: scala recursion tail-recursion


    【解决方案1】:

    递归是方法调用自身的时候。直接递归是指方法直接调用自身。尾调用是在方法中评估的最后一个调用。尾递归是递归调用,也是尾调用。 Direct Tail-Recursion 是直接递归调用,也是 Tail-Call。

    Scala 只保证优化直接尾递归。这是因为至少 Scala 打算运行的一些平台(尤其是 JVM)对高级控制流的支持有限。例如,JVM 只支持内部方法GOTO,这意味着为了实现比直接尾递归更强大的东西,你会遇到 Clojure 的设计者 Rich Hickey 解释为:互操作性,性能,高级控制流 - 选择两个。 Scala 的设计者选择了 Interop 和 Performance,而不是 Proper Tail Calls、Mutual Tail-Recursion、Tail-Recursion Modulo Cons 或类似的更强大的保证。

    [注意:你不能在 JVM 上实现正确的尾调用的经常重复的口头禅是不正确的。 JVM 上有大量的 Scheme 实现证明并非如此。 真实的是JVM 规范本身 不保证正确的尾调用。但是有种方式来实现它们,即:不要使用JVM的调用栈,实现你自己的。但是,这将在 Performance 或 Java Interop 上花费你高昂的成本,可能两者兼而有之。 Scala 的 TailCall 库是另一个如何在 JVM 上实现正确尾调用的示例:Trampolines。]

    initConnection 的第一个版本中,递归调用不是尾调用,因为评估的最后一个调用是对response.onFailure 的调用。

    initConnection 的第二个版本中,根本没有递归调用,尾调用是到getData


    Tail-Calls 本质上等同于GOTO,Tail-Recursion 本质上等同于while 循环(在JVM 上使用GOTO 实现)。但是,JVM 的GOTO 仅在in 单个方法中工作。因此,如果 Scala 想要实现正确的尾递归,他们要么必须实现自己的堆栈而不使用 JVM,将所有相互递归的方法组合成一个巨大的方法,这样GOTO 就可以工作,使用异常作为蹦床或者做类似的讨厌的事情,所有这些都会破坏 Java 互操作性、性能或两者。

    由于 JVM 是 Scala 设计者的重要平台,而性能和平台互操作性是重要目标,他们宁愿放弃有用的语言特性也不愿放弃强大的平台。在规范中强制要求适当的尾递归将使 Scala 在 2015 年之前的 JVM 或 ECMAScript 等平台上几乎无法实现。 (或者更确切地说,它会禁止高性能、高度互操作的实现。)

    【讨论】:

    • 相互(尾)递归没有优化?
    • 谢谢,这回答了我的问题。
    猜你喜欢
    • 2021-04-15
    • 2019-09-14
    • 1970-01-01
    • 2012-12-28
    • 2016-12-20
    • 1970-01-01
    • 2016-06-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多