黑猴子的家:Scala 用于优化的注解(尾递归优化)

Scala类库中的有些注解,可以控制编译器优化,即将开始介绍

1、尾递归 @tailrec

object Module_WeiDG {
  @tailrec 
  def story(): Unit = {
    println("从前有座山,山上有座庙,庙里有个老和尚,一天老和尚对小和尚讲故事")
    story()
  }
}

尖叫提示:进入下一个函数,不再需要上一个函数的环境了,得出结果以后直接返回。尾递归调用,可以被转化成循环,这样能节约栈空间

2、非尾递归

def story(): Unit = { 
  println("从前有座山,山上有座庙,庙里有个老和尚,一天老和尚对小和尚讲故事")
  story()
  println("小和尚听了,找了一块豆腐撞死了")
}

尖叫提示:下一个函数结束以后,此函数还有后续,所以必须保存本身的环境以供处理返回值。

3、尾递归优化案例

递归调用有时能被转化为循环,这样能节约栈空间。在函数式编程中,这是很重要的,我们通常会使用递归方法来遍历集合

(1)非尾递归

object Util {
    def sum(xs: Seq[Int]): BigInt = {
        if (xs.isEmpty) 0 else xs.head + sum(xs.tail)
    }
        ...
}

上面的sum方法无法被优化,因为计算过程中最后一步是加法,而不是递归调用。调整后的代码

(2)尾递归优化

def sum2(xs: Seq[Int], partial: BigInt): BigInt = {
    if (xs.isEmpty) partial else sum2(xs.tail, xs.head + partial)
}

Scala编译器会自动对sum2应用“尾递归”优化。如果你调用sum(1 to 1000000) 将会发生一个栈溢出错误。不过sum2(1 to 1000000, 0) 将会得到正确的结果。

尽管Scala编译器会尝试使用尾递归优化,但有时候某些不太明显的原因会造成它无法这样做,比如比较复杂的逻辑,如果你想编译器无法进行优化时报错,则应该给你的方法加上@tailrec注解,这样一来,如果编译器无法应用该优化,它就会报错

(3)蹦床

蹦床,感兴趣可以自己多了解了解

import scala.util.control.TailCalls._
def evenLength(xs: Seq[Int]): TailRec[Boolean] = {
  if(xs.isEmpty) done(true) else tailcall(oddLength(xs.tail))
}

def oddLength(xs: Seq[Int]): TailRec[Boolean] = {
  if(xs.isEmpty) done(false) else tailcall(evenLength(xs.tail))
}

// 获得TailRec对象获取最终结果,可以用result方法
evenLength(1 to 1000000).result

尖叫提示:对于消除递归,一个更加通用的机制叫做“蹦床”。蹦床的实现会将执行一个循环,不停的调用函数。每个函数都返回下一个将被调用的函数。尾递归在这里是一个特例,每个函数都返回它自己。

Scala有一个名为TailCalls的工具对象,帮助我们轻松实现蹦床。相互递归的函数返回类型为TailRec[A],其要么返回done(result),要么返回tailcall(fun)。其中,fun是下一个被调用的函数。这必须是一个不带额外参数且同样返回TailRec[A]的函数上面是一个简单的示例

你可能感兴趣的:(黑猴子的家:Scala 用于优化的注解(尾递归优化))