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

Trying to solidify tail recursion understanding in Scala

我正在复习 Scala 考试,并试图找出我错过的这个测验问题。我将尾递归理解为 "last call is itself",但我对其中一些代码片段之间的区别感到困惑。 为什么这被认为是尾递归,

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 到底有什么限制它不能尾递归的?如果这是一个愚蠢的问题,我很抱歉,我没有看到数字的影响并且想巩固我的理解。任何其他完全能够识别尾递归的指针也会很棒!

在第二个片段中,最后一次调用 不是"itself",而是+。 您必须先从 f(x+1) 获得结果,然后 然后 在 return 之前将结果加 1。因此,当前帧必须保留在堆栈中以供 f(x+1) 调用。

相反,在第一个片段中,在调用 f(x+1) 之后没有什么可做的,因为它的结果变成了 return 值。因此,编译器可以在 进行调用之前从堆栈中移除当前帧 ,这样 f(x+1) 调用的结果将直接 returned 到调用者f(x).

你的理解不太对。尾递归并不意味着"last call is itself"而是"calling itself is last"。也就是说,尾递归调用必须是函数执行的最后一个动作。它必须是函数在该代码路径上执行的操作列表的 "tail"。 (当然,必须有一个不包含递归调用的代码路径)。

考虑表达式中值的计算方式而不是它们在代码中出现的顺序也很重要。这个表达式

1 + f(x + 1)

按顺序执行:

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

或者,

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

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

用函数调用符号明确写下所有内容通常会有所帮助:

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 吗?