C#程序员的Scala之路第七章(尾递归)

尾递归

Scala为了让我们避免使用while与var,而去使用递归去解决while的问题,但是递归每次都要重新分配堆栈。

Scala编译器在此作了优化,如果一个函数在函数的最后调用了自己,相当于就是跳转到函数的开始,这样,编译器就可以避免分配堆栈而优化了。

下面函数是一个经典的递归,每次递归的时候都会分配堆栈:

object LongLines extends App {
  def boom(x: Int): Int =
    if (x == 0) throw new Exception("boom !") else { boom(x - 1) +1 } boom(3) }

执行以后输出异常信息:

Exception in thread "main" java.lang.Exception: boom !
at LongLines$.boom(Hello.scala:3)
at LongLines$.boom(Hello.scala:5)
at LongLines$.boom(Hello.scala:5)
at LongLines$.boom(Hello.scala:5)
at LongLines$.delayedEndpoint$LongLines$1(Hello.scala:7)
at LongLines$delayedInit$body.apply(Hello.scala:1)
at scala.Function0$class.apply$mcV$sp(Function0.scala:40)
at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12)
at scala.App$$anonfun$main$1.apply(App.scala:76)
at scala.App$$anonfun$main$1.apply(App.scala:76)
at scala.collection.immutable.List.foreach(List.scala:381)
at scala.collection.generic.TraversableForwarder$class.foreach(TraversableForwarder.scala:35)
at scala.App$class.main(App.scala:76)
at LongLines$.main(Hello.scala:1)
at LongLines.main(Hello.scala)

我们看到他调用了4次的boom方法,这是一个典型的递归操作

因为他在调用boom(x-1)之后执行了+1操作,编译器不会把他判定为尾递归函数

下面就把+1去掉:

object LongLines extends App {
  def boom(x: Int): Int =
    if (x == 0) throw new Exception("boom !") else { boom(x - 1) } boom(3) }

这样就是一个尾递归函数了,看异常输出:

Exception in thread "main" java.lang.Exception: boom !
at LongLines$.boom(Hello.scala:3)
at LongLines$.delayedEndpoint$LongLines$1(Hello.scala:7)
at LongLines$delayedInit$body.apply(Hello.scala:1)
at scala.Function0$class.apply$mcV$sp(Function0.scala:40)
at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12)
at scala.App$$anonfun$main$1.apply(App.scala:76)
at scala.App$$anonfun$main$1.apply(App.scala:76)
at scala.collection.immutable.List.foreach(List.scala:381)
at scala.collection.generic.TraversableForwarder$class.foreach(TraversableForwarder.scala:35)
at scala.App$class.main(App.scala:76)
at LongLines$.main(Hello.scala:1)
at LongLines.main(Hello.scala)

只调用了一次boom方法,尾递归在很多编译器中都有应用,Scala让我们大胆的使用尾递归,不会有性能上的损耗。

但是不是所有递归都能被识别成尾递归的,比如嵌套的递归:

object LongLines extends App {
  def isEven(x: Int): Boolean = 
    if (x == 0) true else isOdd(x - 1) def isOdd(x: Int): Boolean = if (x == 0) false else isEven(x - 1) }

如递归最后调用的是函数参数:

object LongLines extends App {
  val funValue = nestedFun _ def nestedFun(x: Int) { if (x != 0) { println(x); funValue(x - 1) } } }

这几种编译器都是不能优化的。

更多请参考Scala编程

你可能感兴趣的:(scala)