循环需要变异,因为它一直在改变变量的值,Scala讨厌变异,为什么?
什么是突变?
突变就是改变一个对象、变量,是常见的副作用之一。
现在问题来了,为什么Scala讨厌突变?
突变可能会导致模棱两可、意料之外的错误,以及调试问题时的困难。突变使代码更难破译。所以一个对象或数据结构的值随时都可能发生变化,因此我们在阅读代码时必须谨慎。
由于Scala支持Immutability,Immutability是Scala的基本特征,这就是Scala讨厌突变的原因。


现在我们知道为什么Scala讨厌突变了,那么解决办法是什么呢?
解决方案就是递归。现在问题来了
什么是递归?
递归是计算机科学中广泛使用的一种现象,用来解决复杂的问题,把它们分解成更简单的问题。递归是一个函数直接或间接地调用自己的过程。相应的函数被称为递归函数。
什么是基本条件?
提供基础条件的解决方案,大问题的解决方案用小问题来表示。


让我们举一个例子来证明递归的作用 factorial
def factorial(n: Int): Int = {
if(n<=1) 1 // base condition
else n * factorial(n-1) // calling function recursively
}
println(factorial(5))


递归的问题
- 它需要大量的内存空间来保存系统堆栈中的中间结果。
- 在空间和时间复杂度方面,它的效率较低。
- 如果基类无法到达或没有定义,那么就会出现堆栈溢出的问题。
- 递归函数通常比非递归函数慢。


堆栈溢出崩溃
为什么会出现堆栈溢出错误?
在一个递归函数中,每个函数的调用都会在内存中创建自己的堆栈框架,并且在递归函数的最后一次调用解决之前,函数的第一次调用不会返回。
因此,如果函数 F 递归调用自己 N 次,那么它将为函数 F 的一次执行在堆栈内存中创建 N 个堆栈框架,编译器将在内存中保留所有 N 个堆栈框架,直到递归函数的最后一次调用。
如何解决 ?
为了解决堆栈溢出错误,我们必须使用尾部递归。所以现在问题来了 什么是尾部递归?
当一个函数执行的最后一个操作是递归调用时,就可以说它是一个尾部递归的函数。由于我们使用一个累加器来计算中间结果,所以不需要跟踪以前的结果。因此,堆栈内存的利用率并不高。
我们使用@tailrec注解来检查一个函数是否是尾部递归的。但是,如果我们使用@tailrec注解,而该函数不是尾部递归的,那么,它将抛出一个编译时错误。
因此,让我们在一个例子的帮助下理解@tailrec


通过尾部递归处理Stack Overflow崩溃
总结
我们知道,循环会导致突变,而scala坚持不变性原则,因此在scala中递归函数比循环更受欢迎。
因此,当你需要循环时,请使用递归,当你需要递归时。尝试使用尾部递归。