定义
直接或间接调用函数本身的函数称为递归函数。
递归是函数式编程的特点,也是优雅地实现很多算法的强大工具。在函数式编程中递归是实现“循环”的唯一方法,这是因为你无法修改循环变量。
缺点
- 反复调用函数带来的开销;
- 栈溢出的风险。
尾递归
在递归函数中,若递归调用是整个函数中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归函数就是尾递归。
尾递归是一种能把函数优化为循环的最重要的一种递归,可以消除递归函数潜在的栈溢出风险,并消除函数调用开销进而提升效率。
JVM和许多其它语言环境目前并不对尾递归做优化,但scalac则可以。
实例
import scala.annotation.tailrec
def factorial(i: BigInt) = {
@tailrec
def fact(i: BigInt, accumulator: BigInt): BigInt = {
if(1 == i) accumulator
else fact(i - 1, i * accumulator)
}
fact(i, 1)
}
将标记@tailrec添加到尾递归函数时,编译器会对其做尾递归优化。若添加此标记的函数并不是尾递归函数,则编译器会抛出错误。
当一个调用了自身的方法,有可能被子类型中的同名方法覆写时,尾递归是无效的。所以,尾递归的方法必须用private或final关键字声明,或者将其嵌套在另一个方法中。
Scala采用的是局部作用域类型推断,无法推断出递归函数的返回类型。因此需要显式写出其返回类型。
定义一个嵌套的尾递归函数,将累加值作为参数,是将很多普通递归算法转化为尾递归的实用技巧。
Trampoline类型递归函数
Trampoline(原意“蹦床”):是指通过依次调用各个函数完成一系列函数之间的循环,也即在多个函数之间反复来回调用。Scala库中的尾递归对象(scala.util.control.TailCalls)可以将trampoline转化为循环。
import scala.util.control.TailCalls._
def isEven(xs: List[Int]): TailRec[Boolean] = {
if(xs.isEmpty) done(true)
else tailCall(isOdd(xs.tail))
}
def isOdd(xs: List[Int]): TailRec[Boolean] = {
if(xs.isEmpty) done(false)
else tailCall(isEven(xs.tail))
}
isEven((1 to 2).toList).result //true
参考《Scala程序设计》第二版