试图巩固Scala中的尾递归理解

时间:2019-02-18 17:36:24

标签: scala tail-recursion

我正在为Scala的考试做复习,并试图找出我错过的测验问题。我将尾递归理解为“最后一次调用本身”,但是我对其中一些代码片段之间的差异感到困惑。 为什么将此视为尾递归,

def f(x: Int): Int = {
    if (x % 2 == 0) {1} 
    else {f(x + 1)}

但这不是吗?

def f(x: Int): Int = {
    if (x % 2 == 0) {1} 
    else {1 + f(x + 1)}

在函数中加1到底是什么限制了它不能进行尾递归?很抱歉,如果这是一个愚蠢的问题,我看不到这个数字有何影响,并希望巩固我的理解。完全能够识别尾递归的任何其他指标也很棒!

3 个答案:

答案 0 :(得分:6)

您的理解不太正确。尾递归并不意味着“最后一次呼叫本身”,而是“最后一次呼叫本身”。也就是说,尾递归调用必须是该函数执行的最后一个动作。它必须是函数在该代码路径上执行的操作列表的“尾部”。 (当然,必须有一个不包含递归调用的代码路径。)

考虑表达式中的值如何求值,而不是它们在代码中出现的顺序,也很重要。这个表情

1 + f(x + 1)

按以下顺序执行:

tmp1 = x + 1
tmp2 = f(tmp1)
res = 1 + tmp2

或者,

add(1, f(add(x, 1))

这样写,您可以看到对f的调用之后是另一个动作,即最后的+ / add。由于递归调用不是最后的动作,所以它不是尾递归。

答案 1 :(得分:5)

在第二个片段中,最后一次通话不是“自身”,而是+。 您必须先从f(x+1)获取结果,然后然后将其加1,然后再返回。因此,当前帧必须保留在堆栈中以进行f(x+1)调用。

相反,在第一个代码段中,f(x+1)被调用后没有任何事情要做,因为其结果成为返回值。因此,编译器可以在进行调用之前从堆栈 中删除当前帧,以便将f(x+1)调用的结果直接返回给f(x)的调用者。

答案 2 :(得分:2)

通常可以用函数调用符号将所有内容明确记下来:

def f(x: Int): Int =
  if (==(%(x, 2), 0) 1 
  else f(+(x, 1))

def f(x: Int): Int =
  if (==(%(x, 2), 0) 1 
  else +(1, f(+(x, 1)))

您现在可以发现为什么在第二个示例中,最后一次调用没有到达f吗?