【问题标题】:Is there a way to optimize tail calls in Scala?有没有办法在 Scala 中优化尾调用?
【发布时间】:2021-01-24 08:00:19
【问题描述】:

我知道 Scala 对尾递归函数(即递归调用是函数执行的最后一件事的函数)进行了优化。我在这里要问的是是否有办法优化对不同函数的尾调用。考虑以下Scala 代码:

def doA(): Unit = {
  doB()
}

def doB(): Unit = {
  doA()
}

如果我们让它执行足够长的时间,它会产生一个堆栈溢出错误,可以通过分配更多的堆栈空间来缓解这个错误。尽管如此,它最终会超出分配的空间,并再次导致堆栈溢出错误。缓解这种情况的一种方法可能是:

case class C(f: () => C)

def run(): Unit = {
  var c: C = C(() => doA())
  while(true){
    c = c.f.apply()
  }
}

def doA(): C = {
  C(() => doB())
}

def doB(): C = {
  C(() => doA())
}

然而,这被证明是相当缓慢的。有没有更好的方法来优化它?

【问题讨论】:

  • 你看过标准库中的TailCalls 吗? (在this recent answer中也提到过。)
  • 不是,不是。我猜它应该比我建议的更有效,对吧?我应该注意这方面的任何限制吗?
  • 根据the accompanying paper“......众所周知的蹦床技术......允许我们以编程方式将堆栈交换为堆。” 所以虽然你不应该'如果没有 StackOverflow,分配仍然会有一些有限的限制。
  • 我加倍。库提供的所有实现延续的方法(以及语言编译器提供的许多方法)都依赖于分配一些用于暂停计算的中间类型。我想手动重写代码以使用while@tailrec 是唯一“快速”的方式。
  • 目的是什么?这种交叉递归是必需的吗?有许多不同的方法可以在 2 个(或更多)函数之间创建无限交替。

标签: scala recursion tail-recursion tail-call-optimization tail-call


【解决方案1】:

这里有一种方法可以无限进行方法调用,而无需消耗堆栈,其中每个方法决定下一个方法。

def doA(): () => Any = {
  doB _
}
def doB(): () => Any = {
  doC _
}
def doC(): () => Any = {
  if (util.Random.nextBoolean()) doA _
  else                           doB _
}

Iterator.iterate(doA())(_.asInstanceOf[() => () => Any]())
        .foreach(identity)

【讨论】:

    猜你喜欢
    • 2021-05-30
    • 2019-07-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-30
    • 2020-12-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多