@tailrec是如何工作的

时间:2013-06-26 11:59:59

标签: scala

我已经使用并阅读了关于@tailrec注释的尾部递归方法。我已经通过许多链接解释了它。例如,它仅适用于自调用函数,不应覆盖等。

在任何地方都提到了compiler optimizes。但是编译器做了什么魔术/概念来使其尾递归。对于下面的简单函数,编译器会做什么:

@tailrec def fact(acc: Int, n: Int): Int = {
  if (n <= 1) acc
  else fact(n * acc, n - 1)
}
fact(1,10)

我的意思是它将它转换为循环,重复调用它然后返回最终值?是否有任何链接到纸张解释它

2 个答案:

答案 0 :(得分:11)

除了我对你的问题的评论(在这里重新编写代码):

  var acc = 1 
  var n = 10
start: 
  if (n <= 1) return acc 
  else { 
    acc = n * acc
    n = n - 1
    goto start
  }

我尝试使用最近的构建编译fact方法,我恰好拥有scalac -Xprint:all并且不知何故编译器发出了icode文件。所以这真实地说明了它如何优化尾调用:

  // methods
  def fact(acc: Int (INT), n: Int (INT)): Int {
  locals: value acc, value n, value _$this
  startBlock: 1
  blocks: [1,2,3,4,5]

  1: 
    2   JUMP 2

  2: // huynhjl's comment: IF condition is here
    3   LOAD_LOCAL(value n)
    3   CONSTANT(1)
    3   CJUMP (INT)LE ? 3 : 4

  3: // huynhjl's comment: first branch of IF, will return acc
    3   LOAD_LOCAL(value acc)
    3   JUMP 5

  5: 
    2   RETURN(INT)

  4: // huynhjl's comment: else branch of IF, update acc and n and jump back
    4   LOAD_LOCAL(value n)
    4   LOAD_LOCAL(value acc)
    4   CALL_PRIMITIVE(Arithmetic(MUL,INT))
    4   LOAD_LOCAL(value n)
    4   CONSTANT(1)
    4   CALL_PRIMITIVE(Arithmetic(SUB,INT))
    4   STORE_LOCAL(value n)
    4   STORE_LOCAL(value acc)
    4   JUMP 2

  }

答案 1 :(得分:8)

这是一个关于主题的好Blog post

@tailrec仅保证在编译器无法执行尾调用优化时发出错误。 Scala默认执行尾调用。

当满足论文描述的条件时,可以保留最后一帧而不是帧的堆栈,并执行循环。更好地描述了该过程here